Посмотрите видео ниже, чтобы узнать, как установить наш сайт как веб-приложение на главный экран.
Примечание: Эта функция может быть недоступна в некоторых браузерах.
Оптимизировать надо не DLE, а VPSВсем привет. Как можно оптимизировать движок DLE если на сайте 40 000 новостей с постерами?
3) В базе сделай внешние ключи между таблицами связанными + uniq ключи поставь по уникальным полям это ускоряет запросы у DLE стокового треш там
Так есть универсальное решение правки базы? Думаю многим было бы полезно. Готов поддержать на чай с печенкамиsneiks Ну на холостых не заметишь разницы, тут надо нагрузочные тесты делать, но ускорять будет в любом случае базу наличие грамотно расставленных ключей. Но там и сама база не нормализована у DLE структура базы на ужасном уровне
ждёмsneiks ну я как нибудь сделаю плагин оптимизатор DLE если руки дойдут, пока некогда
<?xml version="1.0" encoding="utf-8"?>
<dleplugin>
<name>DBOptimizerPro</name>
<description><![CDATA[
Оптимизация БД DLE: индексы, UNIQUE, FOREIGN KEY, обслуживание таблиц (ANALYZE/CHECK/OPTIMIZE/REPAIR).
Как открыть:
- Админка → Сторонние модули → DBOptimizerPro
- или /admin.php?mod=db_optimizer
Важно:
- ALTER TABLE/OPTIMIZE может быть долгим и блокировать таблицы.
- Перед применением сделайте бэкап.
- Лог: /engine/data/dboptimizerpro.log
]]></description>
<icon></icon>
<version>1.1.0</version>
<dleversion>18.0</dleversion>
<versioncompare>greater</versioncompare>
<upgradeurl></upgradeurl>
<filedelete>0</filedelete>
<needplugin></needplugin>
<mnotice>0</mnotice>
<mysqlinstall><![CDATA[
INSERT IGNORE INTO `{prefix}_admin_sections` (`name`, `title`, `descr`, `icon`, `allow_groups`)
VALUES ('db_optimizer', 'DBOptimizerPro', 'Оптимизация БД (индексы, UNIQUE, FOREIGN KEY)', '', '1');
]]></mysqlinstall>
<mysqlupgrade><![CDATA[]]></mysqlupgrade>
<mysqlenable><![CDATA[]]></mysqlenable>
<mysqldisable><![CDATA[]]></mysqldisable>
<mysqldelete><![CDATA[
DELETE FROM `{prefix}_admin_sections` WHERE `name`='db_optimizer';
]]></mysqldelete>
<phpinstall><![CDATA[
@unlink(ENGINE_DIR . '/cache/system/adminmenu.php');
]]></phpinstall>
<phpupgrade><![CDATA[]]></phpupgrade>
<phpenable><![CDATA[]]></phpenable>
<phpdisable><![CDATA[]]></phpdisable>
<phpdelete><![CDATA[
@unlink(ENGINE_DIR . '/cache/system/adminmenu.php');
]]></phpdelete>
<notice><![CDATA[]]></notice>
<file name="engine/inc/db_optimizer.php">
<operation action="create">
<replacecode><![CDATA[
<?php
/**
* DBOptimizerPro - модуль админки DLE
* Версия: 1.1.0
*
* Реализовано:
* - Обслуживание таблиц: ANALYZE / CHECK / OPTIMIZE / REPAIR (REPAIR только MyISAM)
* - Индексы (обычные)
* - UNIQUE (с проверкой дублей)
* - FOREIGN KEY (с проверками: InnoDB, типы, индекс, сироты)
*
* Важно:
* - FOREIGN KEY сам по себе не "ускоритель". Чаще ускоряют индексы.
* - Любой ALTER TABLE/OPTIMIZE может быть долгим и блокировать таблицы.
* - Сделайте бэкап перед применением.
*/
if (!defined('DATALIFEENGINE') || !defined('LOGGED_IN')) {
die('Hacking attempt!');
}
if (!isset($member_id) || !is_array($member_id) || (int)$member_id['user_group'] !== 1) {
if (function_exists('msg')) {
msg('error', 'Доступ запрещён', 'Модуль доступен только администраторам (группа 1).');
}
die('Access denied');
}
define('DBOP_VERSION', '1.1.0');
// ---------------- helpers ----------------
function dbop_h($v) { return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); }
function dbop_bytes($bytes) {
$bytes = (float)$bytes;
if ($bytes < 1024) return sprintf('%.0f B', $bytes);
$u = array('KB','MB','GB','TB','PB');
$i = 0;
$bytes /= 1024;
while ($bytes >= 1024 && $i < count($u)-1) { $bytes /= 1024; $i++; }
return sprintf('%.2f %s', $bytes, $u[$i]);
}
function dbop_escape_like($s) {
return str_replace(array('\\', '%', '_'), array('\\\\', '\\%', '\\_'), (string)$s);
}
function dbop_require_hash() {
global $dle_login_hash;
if (!isset($_REQUEST['user_hash']) || $_REQUEST['user_hash'] !== $dle_login_hash) {
msg('error', 'Ошибка безопасности', 'Неверный ключ безопасности (user_hash). Обновите страницу и повторите действие.');
}
}
function dbop_log($line) {
$file = ENGINE_DIR . '/data/dboptimizerpro.log';
$ts = date('Y-m-d H:i:s');
@file_put_contents($file, '['.$ts.'] '.$line."\n", FILE_APPEND);
}
function dbop_is_safe_identifier($s) {
return (bool)preg_match('/^[A-Za-z0-9_]+$/', (string)$s);
}
// --------- DB introspection ---------
function dbop_get_tables($prefix, $only_prefix=true) {
global $db;
$out = array();
$where = "TABLE_SCHEMA = DATABASE()";
if ($only_prefix && $prefix !== '') {
// хотим только таблицы DLE: PREFIX_*
$like = dbop_escape_like($prefix . '_') . '%';
$where .= " AND TABLE_NAME LIKE '{$like}' ESCAPE '\\\\'";
}
$sql = "SELECT TABLE_NAME, ENGINE, TABLE_ROWS, DATA_LENGTH, INDEX_LENGTH, DATA_FREE
FROM information_schema.TABLES
WHERE {$where}
ORDER BY TABLE_NAME";
$db->query($sql);
while ($r = $db->get_row()) {
$out[$r['TABLE_NAME']] = array(
'table' => $r['TABLE_NAME'],
'engine' => $r['ENGINE'],
'rows' => (int)$r['TABLE_ROWS'],
'data' => (int)$r['DATA_LENGTH'],
'index' => (int)$r['INDEX_LENGTH'],
'free' => (int)$r['DATA_FREE'],
);
}
$db->free();
return $out;
}
function dbop_get_columns($table) {
global $db;
$cols = array();
$db->query("SHOW COLUMNS FROM `{$table}`");
while ($r = $db->get_row()) $cols[] = $r['Field'];
$db->free();
return $cols;
}
function dbop_get_column_types($table) {
global $db;
$types = array();
$db->query("SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '".$db->safesql($table)."'");
while ($r = $db->get_row()) {
$types[$r['COLUMN_NAME']] = array(
'type' => $r['COLUMN_TYPE'],
'null' => $r['IS_NULLABLE'],
'def' => $r['COLUMN_DEFAULT'],
);
}
$db->free();
return $types;
}
function dbop_get_indexes($table) {
global $db;
$idx = array();
$db->query("SHOW INDEX FROM `{$table}`");
while ($r = $db->get_row()) {
$name = $r['Key_name'];
if (!isset($idx[$name])) {
$idx[$name] = array(
'unique' => ((int)$r['Non_unique'] === 0),
'cols' => array(),
);
}
$seq = (int)$r['Seq_in_index'];
$idx[$name]['cols'][$seq] = $r['Column_name'];
}
$db->free();
foreach ($idx as $k => $v) {
ksort($idx[$k]['cols']);
$idx[$k]['cols'] = array_values($idx[$k]['cols']);
}
return $idx;
}
function dbop_has_index_prefix($indexes, $cols, $need_unique=false) {
$n = count($cols);
foreach ($indexes as $idx) {
if ($need_unique && empty($idx['unique'])) continue;
if (count($idx['cols']) < $n) continue;
$ok = true;
for ($i=0; $i<$n; $i++) {
if ((string)$idx['cols'][$i] !== (string)$cols[$i]) { $ok = false; break; }
}
if ($ok) return true;
}
return false;
}
function dbop_fk_exists($table, $col, $ref_table, $ref_col) {
global $db;
$sql = "SELECT CONSTRAINT_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '".$db->safesql($table)."'
AND COLUMN_NAME = '".$db->safesql($col)."'
AND REFERENCED_TABLE_NAME = '".$db->safesql($ref_table)."'
AND REFERENCED_COLUMN_NAME = '".$db->safesql($ref_col)."'
LIMIT 1";
$row = $db->super_query($sql);
return !empty($row['CONSTRAINT_NAME']);
}
function dbop_get_table_engine($tables, $table) {
return isset($tables[$table]['engine']) ? strtoupper((string)$tables[$table]['engine']) : '';
}
// --------- Definitions (indexes, unique, foreign keys) ---------
function dbop_defs($prefix, $tables) {
$p = ($prefix !== '') ? ($prefix . '_') : '';
$t_post = $p . 'post';
$t_comm = $p . 'comments';
$t_users = $p . 'users';
$t_extra = $p . 'post_extras';
$t_tags = $p . 'tags';
$has = function($t) use ($tables) { return isset($tables[$t]); };
$defs = array(
'indexes' => array(),
'unique' => array(),
'fks' => array(),
);
// ---- обычные индексы ----
if ($has($t_post)) {
$defs['indexes'][] = array(
'id' => 'idx_post_approve_date',
'table' => $t_post,
'name' => 'idx_approve_date',
'cols' => array('approve','date'),
'why' => 'Частая выборка: approve=1 + сортировка по date (главная/категории).',
);
$defs['indexes'][] = array(
'id' => 'idx_post_autor',
'table' => $t_post,
'name' => 'idx_autor',
'cols' => array('autor'),
'why' => 'Выборки по автору.',
);
}
if ($has($t_comm)) {
$defs['indexes'][] = array(
'id' => 'idx_comments_post_approve_date',
'table' => $t_comm,
'name' => 'idx_post_approve_date',
'cols' => array('post_id','approve','date'),
'why' => 'Вывод комментариев: post_id + approve + сортировка по date.',
);
}
if ($has($t_tags)) {
$defs['indexes'][] = array(
'id' => 'idx_tags_tag',
'table' => $t_tags,
'name' => 'idx_tag',
'cols' => array('tag'),
'why' => 'Поиск по тегу.',
);
$defs['indexes'][] = array(
'id' => 'idx_tags_news_id',
'table' => $t_tags,
'name' => 'idx_news_id',
'cols' => array('news_id'),
'why' => 'Связь тегов с новостями.',
);
}
// ---- UNIQUE ----
if ($has($t_users)) {
$defs['unique'][] = array(
'id' => 'uq_users_name',
'table' => $t_users,
'name' => 'uq_name',
'cols' => array('name'),
'why' => 'Имя пользователя обычно должно быть уникальным.',
'dup_sql' => "SELECT `name` AS v, COUNT(*) AS c FROM `{$t_users}` GROUP BY `name` HAVING c>1 LIMIT 50",
);
$defs['unique'][] = array(
'id' => 'uq_users_email',
'table' => $t_users,
'name' => 'uq_email',
'cols' => array('email'),
'why' => 'Email часто уникален. Если у вас разрешены одинаковые email - не ставьте.',
'dup_sql' => "SELECT `email` AS v, COUNT(*) AS c FROM `{$t_users}` WHERE `email`<>'' GROUP BY `email` HAVING c>1 LIMIT 50",
);
}
if ($has($t_extra)) {
$defs['unique'][] = array(
'id' => 'uq_post_extras_news_id',
'table' => $t_extra,
'name' => 'uq_news_id',
'cols' => array('news_id'),
'why' => 'Обычно 1 запись post_extras на 1 новость. Ускоряет JOIN по news_id и защищает от дублей.',
'dup_sql' => "SELECT `news_id` AS v, COUNT(*) AS c FROM `{$t_extra}` GROUP BY `news_id` HAVING c>1 LIMIT 50",
);
}
if ($has($t_tags)) {
$defs['unique'][] = array(
'id' => 'uq_tags_news_tag',
'table' => $t_tags,
'name' => 'uq_news_tag',
'cols' => array('news_id','tag'),
'why' => 'Убирает дубли связей (news_id + tag).',
'dup_sql' => "SELECT CONCAT(`news_id`,':',`tag`) AS v, COUNT(*) AS c FROM `{$t_tags}` GROUP BY `news_id`,`tag` HAVING c>1 LIMIT 50",
);
}
// ---- FOREIGN KEYS ----
if ($has($t_comm) && $has($t_post)) {
$defs['fks'][] = array(
'id' => 'fk_comments_post',
'table' => $t_comm,
'col' => 'post_id',
'ref_table' => $t_post,
'ref_col' => 'id',
'name' => 'fk_comments_post',
'on_delete' => 'CASCADE',
'on_update' => 'CASCADE',
'why' => 'Комментарии должны ссылаться на существующую новость. При удалении новости логично удалять комментарии.',
'orphan_sql'=> "SELECT COUNT(*) AS c FROM `{$t_comm}` c LEFT JOIN `{$t_post}` p ON p.`id`=c.`post_id` WHERE c.`post_id`<>0 AND p.`id` IS NULL",
);
}
if ($has($t_extra) && $has($t_post)) {
$defs['fks'][] = array(
'id' => 'fk_post_extras_post',
'table' => $t_extra,
'col' => 'news_id',
'ref_table' => $t_post,
'ref_col' => 'id',
'name' => 'fk_post_extras_post',
'on_delete' => 'CASCADE',
'on_update' => 'CASCADE',
'why' => 'post_extras должен ссылаться на новость. При удалении новости extras должны удаляться.',
'orphan_sql'=> "SELECT COUNT(*) AS c FROM `{$t_extra}` e LEFT JOIN `{$t_post}` p ON p.`id`=e.`news_id` WHERE e.`news_id`<>0 AND p.`id` IS NULL",
);
}
if ($has($t_tags) && $has($t_post)) {
$defs['fks'][] = array(
'id' => 'fk_tags_post',
'table' => $t_tags,
'col' => 'news_id',
'ref_table' => $t_post,
'ref_col' => 'id',
'name' => 'fk_tags_post',
'on_delete' => 'CASCADE',
'on_update' => 'CASCADE',
'why' => 'Связи тегов должны ссылаться на существующую новость.',
'orphan_sql'=> "SELECT COUNT(*) AS c FROM `{$t_tags}` t LEFT JOIN `{$t_post}` p ON p.`id`=t.`news_id` WHERE t.`news_id`<>0 AND p.`id` IS NULL",
);
}
return $defs;
}
// ------------- Validation for defs -------------
function dbop_validate_index_def($def, $tables) {
$table = $def['table'];
if (!isset($tables[$table])) return array(false, 'Таблица не найдена');
$cols = dbop_get_columns($table);
$set = array_flip($cols);
foreach ($def['cols'] as $c) {
if (!isset($set[$c])) return array(false, 'Нет колонки: '.$c);
}
$idxs = dbop_get_indexes($table);
if (dbop_has_index_prefix($idxs, $def['cols'], false)) return array(false, 'Индекс уже есть');
return array(true, '');
}
function dbop_validate_unique_def($def, $tables) {
$table = $def['table'];
if (!isset($tables[$table])) return array(false, 'Таблица не найдена');
$cols = dbop_get_columns($table);
$set = array_flip($cols);
foreach ($def['cols'] as $c) {
if (!isset($set[$c])) return array(false, 'Нет колонки: '.$c);
}
$idxs = dbop_get_indexes($table);
if (dbop_has_index_prefix($idxs, $def['cols'], true)) return array(false, 'UNIQUE уже есть');
global $db;
if (!empty($def['dup_sql'])) {
$db->query($def['dup_sql']);
$row = $db->get_row();
$hasDup = !empty($row);
$db->free();
if ($hasDup) return array(false, 'Есть дубли (сначала исправьте)');
}
return array(true, '');
}
function dbop_validate_fk_def($def, $tables) {
global $db;
$t = $def['table'];
$rt = $def['ref_table'];
$c = $def['col'];
$rc = $def['ref_col'];
if (!isset($tables[$t]) || !isset($tables[$rt])) return array(false, 'Таблица/ссылка не найдена');
$eng1 = dbop_get_table_engine($tables, $t);
$eng2 = dbop_get_table_engine($tables, $rt);
if ($eng1 !== 'INNODB' || $eng2 !== 'INNODB') return array(false, 'Требуется InnoDB (обе таблицы)');
$cols1 = array_flip(dbop_get_columns($t));
$cols2 = array_flip(dbop_get_columns($rt));
if (!isset($cols1[$c])) return array(false, 'Нет колонки '.$c.' в '.$t);
if (!isset($cols2[$rc])) return array(false, 'Нет колонки '.$rc.' в '.$rt);
$types1 = dbop_get_column_types($t);
$types2 = dbop_get_column_types($rt);
$ct1 = isset($types1[$c]['type']) ? strtolower($types1[$c]['type']) : '';
$ct2 = isset($types2[$rc]['type']) ? strtolower($types2[$rc]['type']) : '';
if ($ct1 && $ct2 && $ct1 !== $ct2) {
return array(false, 'Типы колонок не совпадают: '.$ct1.' vs '.$ct2);
}
$idx1 = dbop_get_indexes($t);
$hasChildIndex = dbop_has_index_prefix($idx1, array($c), false) || dbop_has_index_prefix($idx1, array($c), true);
if (!$hasChildIndex) return array(false, 'Нужен индекс на '.$t.'.'.$c);
if (!empty($def['orphan_sql'])) {
$row = $db->super_query($def['orphan_sql']);
$cnt = isset($row['c']) ? (int)$row['c'] : 0;
if ($cnt > 0) return array(false, 'Есть сироты: '.$cnt);
}
if (dbop_fk_exists($t, $c, $rt, $rc)) return array(false, 'FK уже существует');
return array(true, '');
}
// --------- Apply operations ---------
function dbop_apply_index($def) {
global $db;
$table = $def['table'];
$name = $def['name'];
$cols = $def['cols'];
if (!dbop_is_safe_identifier($name)) return array(false, 'Плохое имя индекса');
foreach ($cols as $c) if (!dbop_is_safe_identifier($c)) return array(false, 'Плохая колонка');
$cols_sql = '`' . implode('`,`', $cols) . '`';
$sql = "ALTER TABLE `{$table}` ADD INDEX `{$name}` ({$cols_sql})";
dbop_log('Alter: '.$sql);
$db->query($sql);
return array(true, $sql);
}
function dbop_apply_unique($def) {
global $db;
$table = $def['table'];
$name = $def['name'];
$cols = $def['cols'];
if (!dbop_is_safe_identifier($name)) return array(false, 'Плохое имя индекса');
foreach ($cols as $c) if (!dbop_is_safe_identifier($c)) return array(false, 'Плохая колонка');
$cols_sql = '`' . implode('`,`', $cols) . '`';
$sql = "ALTER TABLE `{$table}` ADD UNIQUE INDEX `{$name}` ({$cols_sql})";
dbop_log('Alter: '.$sql);
$db->query($sql);
return array(true, $sql);
}
function dbop_apply_fk($def) {
global $db;
$t = $def['table'];
$c = $def['col'];
$rt = $def['ref_table'];
$rc = $def['ref_col'];
$name = $def['name'];
foreach (array($c,$rc,$name) as $x) {
if (!dbop_is_safe_identifier($x)) return array(false, 'Плохой идентификатор');
}
$onDel = !empty($def['on_delete']) ? strtoupper($def['on_delete']) : 'RESTRICT';
$onUpd = !empty($def['on_update']) ? strtoupper($def['on_update']) : 'RESTRICT';
$allowed = array('RESTRICT','CASCADE','SET NULL','NO ACTION');
if (!in_array($onDel, $allowed, true)) $onDel = 'RESTRICT';
if (!in_array($onUpd, $allowed, true)) $onUpd = 'RESTRICT';
$sql = "ALTER TABLE `{$t}` ADD CONSTRAINT `{$name}` FOREIGN KEY (`{$c}`) REFERENCES `{$rt}`(`{$rc}`) ON DELETE {$onDel} ON UPDATE {$onUpd}";
dbop_log('Alter: '.$sql);
$db->query($sql);
return array(true, $sql);
}
function dbop_convert_engine($table, $engine) {
global $db;
$engine = strtoupper($engine);
if (!in_array($engine, array('INNODB','MYISAM'), true)) return array(false, 'Недопустимый engine');
$sql = "ALTER TABLE `{$table}` ENGINE={$engine}";
dbop_log('Alter: '.$sql);
$db->query($sql);
return array(true, $sql);
}
// ---------------- Controller ----------------
$prefix = defined('PREFIX') ? PREFIX : ((isset($config['dbprefix']) && $config['dbprefix']) ? $config['dbprefix'] : '');
$only_prefix = true;
if (isset($_GET['show_all']) && (int)$_GET['show_all'] === 1) $only_prefix = false;
$tables = dbop_get_tables($prefix, $only_prefix);
$defs = dbop_defs($prefix, $tables);
$tab = isset($_REQUEST['tab']) ? (string)$_REQUEST['tab'] : 'home';
$action = isset($_REQUEST['action']) ? (string)$_REQUEST['action'] : '';
function dbop_json_exit($arr) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode($arr, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
exit;
}
// AJAX actions
if ($action) {
dbop_require_hash();
if ($action === 'sections') {
// добавить/убрать из "Сторонние модули" (на случай, если запись не создалась при установке)
global $db;
$row = $db->super_query("SELECT id FROM " . PREFIX . "_admin_sections WHERE name='db_optimizer'");
if ($row) {
$db->query("DELETE FROM " . PREFIX . "_admin_sections WHERE name='db_optimizer'");
@unlink(ENGINE_DIR . '/cache/system/adminmenu.php');
dbop_json_exit(array('success'=>true,'message'=>'Ссылка убрана из меню «Сторонние модули»'));
} else {
$db->query("INSERT IGNORE INTO " . PREFIX . "_admin_sections (name, title, descr, icon, allow_groups)
VALUES ('db_optimizer', 'DBOptimizerPro', 'Оптимизация БД (индексы, UNIQUE, FOREIGN KEY)', '', '1')");
@unlink(ENGINE_DIR . '/cache/system/adminmenu.php');
dbop_json_exit(array('success'=>true,'message'=>'Ссылка добавлена в меню «Сторонние модули»'));
}
}
if ($action === 'maintenance') {
$cmd = strtolower((string)($_POST['cmd'] ?? 'analyze'));
$allowed_cmds = array('analyze'=>'ANALYZE','check'=>'CHECK','optimize'=>'OPTIMIZE','repair'=>'REPAIR');
if (!isset($allowed_cmds[$cmd])) dbop_json_exit(array('success'=>false,'message'=>'Неизвестная команда'));
$sel = isset($_POST['tables']) && is_array($_POST['tables']) ? $_POST['tables'] : array();
$sel = array_values(array_unique(array_map('strval',$sel)));
$safe = array();
foreach ($sel as $t) if (isset($tables[$t])) $safe[] = $t;
if (!$safe) dbop_json_exit(array('success'=>false,'message'=>'Не выбраны таблицы'));
$results = array();
foreach ($safe as $tname) {
$sqlCmd = $allowed_cmds[$cmd];
if ($sqlCmd === 'REPAIR') {
$eng = strtoupper((string)$tables[$tname]['engine']);
if ($eng !== 'MYISAM') {
$results[] = array('table'=>$tname,'status'=>'SKIP','msg'=>'REPAIR только для MyISAM');
continue;
}
}
$q = "{$sqlCmd} TABLE `{$tname}`";
dbop_log('Run: '.$q);
$db->query($q);
while ($r = $db->get_row()) {
$results[] = array('table'=>$r['Table'],'type'=>$r['Msg_type'],'msg'=>$r['Msg_text']);
}
$db->free();
}
dbop_json_exit(array('success'=>true,'message'=>'Готово','results'=>$results));
}
if ($action === 'apply_indexes' || $action === 'apply_unique' || $action === 'apply_fks') {
$items = isset($_POST['items']) && is_array($_POST['items']) ? $_POST['items'] : array();
$items = array_values(array_unique(array_map('strval',$items)));
if (!$items) dbop_json_exit(array('success'=>false,'message'=>'Ничего не выбрано'));
$kind = ($action === 'apply_indexes') ? 'indexes' : (($action === 'apply_unique') ? 'unique' : 'fks');
$defsList = $defs[$kind];
$map = array();
foreach ($defsList as $d) $map[$d['id']] = $d;
$applied = array();
$skipped = array();
foreach ($items as $id) {
if (!isset($map[$id])) { $skipped[] = array('id'=>$id,'reason'=>'unknown'); continue; }
$d = $map[$id];
if ($kind === 'indexes') {
list($ok,$why) = dbop_validate_index_def($d, $tables);
if (!$ok) { $skipped[] = array('id'=>$id,'reason'=>$why); continue; }
list($ok2,$sql) = dbop_apply_index($d);
if (!$ok2) { $skipped[] = array('id'=>$id,'reason'=>$sql); continue; }
$applied[] = $sql;
}
if ($kind === 'unique') {
list($ok,$why) = dbop_validate_unique_def($d, $tables);
if (!$ok) { $skipped[] = array('id'=>$id,'reason'=>$why); continue; }
list($ok2,$sql) = dbop_apply_unique($d);
if (!$ok2) { $skipped[] = array('id'=>$id,'reason'=>$sql); continue; }
$applied[] = $sql;
}
if ($kind === 'fks') {
list($ok,$why) = dbop_validate_fk_def($d, $tables);
if (!$ok) { $skipped[] = array('id'=>$id,'reason'=>$why); continue; }
list($ok2,$sql) = dbop_apply_fk($d);
if (!$ok2) { $skipped[] = array('id'=>$id,'reason'=>$sql); continue; }
$applied[] = $sql;
}
}
dbop_json_exit(array(
'success'=>true,
'message'=>'Операция завершена',
'applied'=>$applied,
'skipped'=>$skipped,
));
}
if ($action === 'convert_engine') {
$items = isset($_POST['tables']) && is_array($_POST['tables']) ? $_POST['tables'] : array();
$items = array_values(array_unique(array_map('strval',$items)));
$target = strtoupper((string)($_POST['target'] ?? 'INNODB'));
if (!in_array($target, array('INNODB','MYISAM'), true)) $target = 'INNODB';
if (!$items) dbop_json_exit(array('success'=>false,'message'=>'Не выбраны таблицы'));
$applied = array();
$skipped = array();
foreach ($items as $t) {
if (!isset($tables[$t])) { $skipped[] = array('table'=>$t,'reason'=>'unknown'); continue; }
$cur = strtoupper((string)$tables[$t]['engine']);
if ($cur === $target) { $skipped[] = array('table'=>$t,'reason'=>'уже '.$target); continue; }
list($ok,$sql) = dbop_convert_engine($t, $target);
if (!$ok) { $skipped[] = array('table'=>$t,'reason'=>$sql); continue; }
$applied[] = $sql;
}
dbop_json_exit(array('success'=>true,'message'=>'Готово','applied'=>$applied,'skipped'=>$skipped));
}
dbop_json_exit(array('success'=>false,'message'=>'Unknown action'));
}
// ---------------- UI (Admin) ----------------
$__name = 'DBOptimizerPro';
$__descr = 'Оптимизация БД (индексы, UNIQUE, FOREIGN KEY)';
echoheader($__name, $__descr);
$adminUrl = $config['admin_path'] ?? 'admin.php';
$userHash = $dle_login_hash;
$box = ($config['version_id'] >= 12) ? 'panel' : 'box';
$boxHeader = ($config['version_id'] >= 12) ? 'panel-heading' : 'box-header';
$boxContent = 'box-content';
$boxFooter = ($config['version_id'] >= 12) ? 'panel-footer' : 'box-footer';
$tabsClass = ($config['version_id'] >= 12) ? 'nav-tabs-solid' : 'nav-tabs-left';
function dbop_tab_link($tab, $label, $icon='') {
$u = 'admin.php?mod=db_optimizer&tab='.$tab.(isset($_GET['show_all']) && (int)$_GET['show_all']===1 ? '&show_all=1' : '');
$i = $icon ? '<i class="'.$icon.'"></i> ' : '';
$active = ((isset($_REQUEST['tab']) && $_REQUEST['tab']===$tab) || (!isset($_REQUEST['tab']) && $tab==='home')) ? ' class="active"' : '';
return '<li'.$active.'><a href="'.$u.'">'.$i.dbop_h($label).'</a></li>';
}
echo '<style>
/* читабельные табы (у вас были "белое на белом") */
.dbop-wrap .nav-tabs>li>a{color:#333 !important; background:#f6f7f9; border:1px solid #e2e5ea; margin-right:6px; border-bottom:none;}
.dbop-wrap .nav-tabs>li.active>a{background:#ffffff !important; color:#000 !important; font-weight:600;}
.dbop-wrap .nav-tabs{border-bottom:1px solid #e2e5ea;}
.dbop-steps .step{border:1px solid #e5e5e5;border-radius:10px;padding:12px;margin:10px 0;background:#fff;}
.dbop-steps .step h4{margin:0 0 6px 0;}
.dbop-small{color:#666;font-size:12px;}
.dbop-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:12px;border:1px solid #ddd;background:#fafafa;}
.dbop-badge.ok{background:#e8fff0;border-color:#bfe8cd;}
.dbop-badge.bad{background:#fff0f0;border-color:#f1c1c1;}
.dbop-badge.warn{background:#fff8e6;border-color:#f2d49a;}
.dbop-table code{font-size:12px;}
</style>';
echo '<div class="dbop-wrap '.$box.'">';
echo ' <div class="'.$boxHeader.'">';
echo ' <ul class="nav nav-tabs '.$tabsClass.'">';
echo dbop_tab_link('home', 'Быстрый старт', 'icon-home');
echo dbop_tab_link('tables', 'Таблицы', 'icon-database');
echo dbop_tab_link('indexes', 'Индексы', 'icon-list');
echo dbop_tab_link('unique', 'UNIQUE', 'icon-check');
echo dbop_tab_link('fks', 'FOREIGN KEY', 'icon-link');
echo dbop_tab_link('docs', 'Инструкция', 'icon-book');
echo ' </ul>';
echo ' </div>';
echo ' <div class="'.$boxContent.'">';
echo '<div class="alert alert-warning" style="margin-bottom:10px;">
<b>DBOptimizerPro v'.dbop_h(DBOP_VERSION).'</b><br>
Изменения структуры (ALTER TABLE, OPTIMIZE) могут быть долгими и блокировать таблицы.
Перед применением сделайте бэкап. Лог: <code>/engine/data/dboptimizerpro.log</code>
</div>';
if ($prefix === '') {
echo '<div class="alert alert-danger"><b>Проблема:</b> не удалось определить PREFIX. Модуль не знает, какие таблицы считать DLE.</div>';
} else {
echo '<div class="alert alert-info" style="margin-bottom:10px;">
<b>Фильтр:</b> сейчас показаны '.($only_prefix ? 'только таблицы DLE (<code>'.dbop_h($prefix).'_*</code>)' : 'все таблицы текущей БД').'.
<a href="admin.php?mod=db_optimizer&tab='.dbop_h($tab).($only_prefix ? '&show_all=1' : '').'">'.($only_prefix ? 'Показать все' : 'Показать только DLE').'</a>
</div>';
}
if ($tab === 'home') {
$totalData = 0; $totalIdx = 0;
foreach ($tables as $t) { $totalData += $t['data']; $totalIdx += $t['index']; }
echo '<div class="dbop-steps">';
echo '<div class="step">
<h4>Сценарий 1. Безопасно и быстро (рекомендуется)</h4>
<div class="dbop-small">Обновить статистику БД и добавить обычные индексы (без UNIQUE и FOREIGN KEY).</div>
<ol style="margin:8px 0 0 18px;">
<li>Вкладка <b>Таблицы</b> → выделите таблицы → <b>ANALYZE</b> → "Выполнить".</li>
<li>Вкладка <b>Индексы</b> → отметьте "можно добавить" → "Добавить выбранные индексы".</li>
</ol>
</div>';
echo '<div class="step">
<h4>Сценарий 2. Уникальность данных (UNIQUE)</h4>
<div class="dbop-small">UNIQUE не применится, если в таблице есть дубли - модуль покажет примеры.</div>
<ol style="margin:8px 0 0 18px;">
<li>Вкладка <b>UNIQUE</b> → проверьте статус.</li>
<li>Если есть дубли - сначала исправьте, потом применяйте.</li>
</ol>
</div>';
echo '<div class="step">
<h4>Сценарий 3. FOREIGN KEY (осторожно)</h4>
<div class="dbop-small">FOREIGN KEY повышает целостность. Требует InnoDB и отсутствие "сирот".</div>
<ol style="margin:8px 0 0 18px;">
<li>Вкладка <b>FOREIGN KEY</b> → убедитесь, что всё "можно добавить".</li>
<li>Если таблицы не InnoDB - вкладка <b>Таблицы</b> → конвертация в InnoDB.</li>
</ol>
</div>';
echo '</div>';
echo '<hr>';
echo '<div class="row"><div class="col-md-6">
<div class="alert alert-info">
<b>Сводка</b><br>
Таблиц: <b>'.count($tables).'</b><br>
Data: <b>'.dbop_h(dbop_bytes($totalData)).'</b><br>
Index: <b>'.dbop_h(dbop_bytes($totalIdx)).'</b><br>
Total: <b>'.dbop_h(dbop_bytes($totalData+$totalIdx)).'</b>
</div>
</div><div class="col-md-6">
<div class="alert alert-default">
<b>Что реально ускоряет чаще всего</b><br>
1) индексы под реальные WHERE/ORDER BY<br>
2) кэш (Redis/Memcached), PHP-FPM, настройки MySQL<br>
3) анализ slow query log и правка запросов
</div>
</div></div>';
}
if ($tab === 'tables') {
echo '<h3 style="margin-top:0;">Таблицы и обслуживание</h3>';
echo '<div class="dbop-small">Команды: <b>ANALYZE</b> (рекомендуется), <b>CHECK</b>, <b>OPTIMIZE</b> (осторожно), <b>REPAIR</b> (только MyISAM).</div>';
echo '<div class="alert alert-warning" style="margin-top:10px;">
<b>OPTIMIZE</b> на InnoDB часто равен перестройке таблицы и может быть очень долгим.
Используйте после массовых удалений или если видите реальную фрагментацию/раздувание.
</div>';
echo '<form id="dbop-maint-form" onsubmit="return false;">';
echo '<input type="hidden" name="user_hash" value="'.dbop_h($userHash).'">';
echo '<div style="margin:8px 0;">';
echo '<select name="cmd" class="uniform">
<option value="analyze">ANALYZE (рекомендуется)</option>
<option value="check">CHECK</option>
<option value="optimize">OPTIMIZE (осторожно)</option>
<option value="repair">REPAIR (только MyISAM)</option>
</select> ';
echo '<button class="btn btn-primary" onclick="dbop_runMaintenance();">Выполнить команду на выбранных таблицах</button>';
echo '</div>';
echo '<table class="table table-normal table-hover dbop-table" style="width:100%;">';
echo '<thead><tr>
<th style="width:30px;"><input type="checkbox" onclick="dbop_toggleAll(this, \'tables[]\');"></th>
<th>Таблица</th>
<th>Engine</th>
<th style="text-align:right;">Rows*</th>
<th style="text-align:right;">Data</th>
<th style="text-align:right;">Index</th>
<th style="text-align:right;">Total</th>
<th style="text-align:right;">Free</th>
</tr></thead><tbody>';
foreach ($tables as $t) {
$total = $t['data'] + $t['index'];
$eng = strtoupper((string)$t['engine']);
$badge = ($eng==='INNODB') ? 'ok' : (($eng==='MYISAM') ? 'warn' : 'warn');
echo '<tr>
<td><input type="checkbox" name="tables[]" value="'.dbop_h($t['table']).'"></td>
<td><code>'.dbop_h($t['table']).'</code></td>
<td><span class="dbop-badge '.$badge.'">'.dbop_h($eng).'</span></td>
<td style="text-align:right;">'.dbop_h($t['rows']).'</td>
<td style="text-align:right;">'.dbop_h(dbop_bytes($t['data'])).'</td>
<td style="text-align:right;">'.dbop_h(dbop_bytes($t['index'])).'</td>
<td style="text-align:right;"><b>'.dbop_h(dbop_bytes($total)).'</b></td>
<td style="text-align:right;">'.dbop_h(dbop_bytes($t['free'])).'</td>
</tr>';
}
echo '</tbody></table>';
echo '<div class="dbop-small">* TABLE_ROWS в InnoDB - оценка, не точное значение.</div>';
echo '</form>';
echo '<hr><h4>Переключить движок таблиц (для FOREIGN KEY нужен InnoDB)</h4>';
echo '<div class="dbop-small">Конвертация - это ALTER TABLE и может быть долгой. Делайте в непиковое время.</div>';
echo '<div style="margin:8px 0;">';
echo '<select id="dbop-engine-target" class="uniform"><option value="INNODB">INNODB</option><option value="MYISAM">MYISAM</option></select> ';
echo '<button class="btn btn-warning" onclick="dbop_convertEngine();">Конвертировать выбранные таблицы</button>';
echo '</div>';
echo '<div id="dbop-maint-result"></div>';
}
if ($tab === 'indexes') {
echo '<h3 style="margin-top:0;">Индексы (обычные)</h3>';
echo '<div class="dbop-small">Индексы обычно безопаснее, чем UNIQUE/FK. Они ускоряют SELECT, но увеличивают размер БД и могут немного замедлить INSERT/UPDATE.</div>';
if (!$defs['indexes']) {
echo '<div class="alert alert-warning">Список рекомендуемых индексов пуст. Обычно это значит, что не найдены таблицы <code>'.dbop_h($prefix).'_post</code>/<code>'.dbop_h($prefix).'_comments</code> и т.д.</div>';
}
$rows = array();
foreach ($defs['indexes'] as $d) {
$table = $d['table'];
$idxs = isset($tables[$table]) ? dbop_get_indexes($table) : array();
list($ok,$reason) = dbop_validate_index_def($d, $tables);
$exists = isset($tables[$table]) ? dbop_has_index_prefix($idxs, $d['cols'], false) : false;
$status = $exists ? array('ok','уже есть') : ($ok ? array('warn','можно добавить') : array('bad',$reason));
$rows[] = array($d, $status);
}
echo '<form id="dbop-index-form" onsubmit="return false;">';
echo '<input type="hidden" name="user_hash" value="'.dbop_h($userHash).'">';
echo '<table class="table table-normal table-hover dbop-table" style="width:100%;">';
echo '<thead><tr>
<th style="width:30px;"></th>
<th>Статус</th>
<th>Таблица</th>
<th>Индекс</th>
<th>Колонки</th>
<th>Зачем</th>
</tr></thead><tbody>';
foreach ($rows as $r) {
$d = $r[0];
$st = $r[1];
$can = ($st[0] === 'warn');
echo '<tr>
<td>'.($can ? '<input type="checkbox" name="items[]" value="'.dbop_h($d['id']).'">' : '<input type="checkbox" disabled>') .'</td>
<td><span class="dbop-badge '.$st[0].'">'.dbop_h($st[1]).'</span></td>
<td><code>'.dbop_h($d['table']).'</code></td>
<td><code>'.dbop_h($d['name']).'</code></td>
<td><code>'.dbop_h(implode(', ', $d['cols'])).'</code></td>
<td>'.dbop_h($d['why']).'</td>
</tr>';
}
echo '</tbody></table>';
echo '<button class="btn btn-success" onclick="dbop_applySelected(\'apply_indexes\', \'#dbop-index-form\');">Добавить выбранные индексы</button>';
echo '<div id="dbop-index-result" style="margin-top:10px;"></div>';
echo '</form>';
}
if ($tab === 'unique') {
echo '<h3 style="margin-top:0;">UNIQUE ключи (с проверкой дублей)</h3>';
echo '<div class="dbop-small">UNIQUE не применится, если уже есть дубликаты. Плагин покажет примеры дублей.</div>';
if (!$defs['unique']) {
echo '<div class="alert alert-warning">Список UNIQUE пуст (скорее всего, таблицы DLE не найдены по префиксу).</div>';
}
$rows = array();
foreach ($defs['unique'] as $d) {
$table = $d['table'];
$idxs = isset($tables[$table]) ? dbop_get_indexes($table) : array();
$exists = isset($tables[$table]) ? dbop_has_index_prefix($idxs, $d['cols'], true) : false;
list($ok,$reason) = dbop_validate_unique_def($d, $tables);
$status = $exists ? array('ok','уже есть') : ($ok ? array('warn','можно добавить') : array('bad',$reason));
$rows[] = array($d,$status);
}
echo '<form id="dbop-unique-form" onsubmit="return false;">';
echo '<input type="hidden" name="user_hash" value="'.dbop_h($userHash).'">';
echo '<table class="table table-normal table-hover dbop-table" style="width:100%;">';
echo '<thead><tr>
<th style="width:30px;"></th>
<th>Статус</th>
<th>Таблица</th>
<th>UNIQUE</th>
<th>Колонки</th>
<th>Комментарий</th>
<th>Дубли (если есть)</th>
</tr></thead><tbody>';
foreach ($rows as $r) {
$d = $r[0]; $st = $r[1];
$can = ($st[0] === 'warn');
$dupPreview = '';
if ($st[0] === 'bad' && strpos($st[1], 'дубли') !== false && !empty($d['dup_sql'])) {
$db->query($d['dup_sql']);
$i = 0;
$list = array();
while (($row = $db->get_row()) && $i < 5) {
$list[] = $row['v'].' (x'.$row['c'].')';
$i++;
}
$db->free();
if ($list) $dupPreview = implode('<br>', array_map('dbop_h', $list));
}
echo '<tr>
<td>'.($can ? '<input type="checkbox" name="items[]" value="'.dbop_h($d['id']).'">' : '<input type="checkbox" disabled>') .'</td>
<td><span class="dbop-badge '.$st[0].'">'.dbop_h($st[1]).'</span></td>
<td><code>'.dbop_h($d['table']).'</code></td>
<td><code>'.dbop_h($d['name']).'</code></td>
<td><code>'.dbop_h(implode(', ', $d['cols'])).'</code></td>
<td>'.dbop_h($d['why']).'</td>
<td>'.($dupPreview ?: '<span class="dbop-small">-</span>').'</td>
</tr>';
}
echo '</tbody></table>';
echo '<button class="btn btn-success" onclick="dbop_applySelected(\'apply_unique\', \'#dbop-unique-form\');">Добавить выбранные UNIQUE</button>';
echo '<div id="dbop-unique-result" style="margin-top:10px;"></div>';
echo '</form>';
}
if ($tab === 'fks') {
echo '<h3 style="margin-top:0;">FOREIGN KEY (внешние ключи)</h3>';
echo '<div class="alert alert-info">
<b>Что будет сделано:</b> добавятся связи между связанными таблицами DLE (например comments.post_id → post.id).<br>
<b>Почему это может не примениться:</b> не InnoDB, разные типы колонок, нет индекса на дочерней колонке, есть "сироты".
</div>';
if (!$defs['fks']) {
echo '<div class="alert alert-warning">Список FOREIGN KEY пуст (скорее всего, таблицы DLE не найдены по префиксу).</div>';
}
$needInno = array();
foreach ($defs['fks'] as $d) {
$needInno[$d['table']] = true;
$needInno[$d['ref_table']] = true;
}
$notInno = array();
foreach (array_keys($needInno) as $t) {
if (isset($tables[$t])) {
$eng = strtoupper((string)$tables[$t]['engine']);
if ($eng !== 'INNODB') $notInno[] = $t.' ('.$eng.')';
}
}
if ($notInno) {
echo '<div class="alert alert-warning"><b>Сейчас не InnoDB:</b><br>'.implode('<br>', array_map('dbop_h', $notInno)).'<br>
Сначала переведите эти таблицы в InnoDB (вкладка "Таблицы").</div>';
} elseif ($defs['fks']) {
echo '<div class="alert alert-success">Нужные таблицы уже InnoDB.</div>';
}
$rows = array();
foreach ($defs['fks'] as $d) {
list($ok,$reason) = dbop_validate_fk_def($d, $tables);
$exists = dbop_fk_exists($d['table'], $d['col'], $d['ref_table'], $d['ref_col']);
$status = $exists ? array('ok','уже есть') : ($ok ? array('warn','можно добавить') : array('bad',$reason));
$orphanInfo = '';
if (!empty($d['orphan_sql'])) {
$row = $db->super_query($d['orphan_sql']);
$cnt = isset($row['c']) ? (int)$row['c'] : 0;
$orphanInfo = ($cnt>0) ? '<span class="dbop-badge bad">сироты: '.dbop_h($cnt).'</span>' : '<span class="dbop-badge ok">сирот нет</span>';
}
$rows[] = array($d,$status,$orphanInfo);
}
echo '<form id="dbop-fk-form" onsubmit="return false;">';
echo '<input type="hidden" name="user_hash" value="'.dbop_h($userHash).'">';
echo '<table class="table table-normal table-hover dbop-table" style="width:100%;">';
echo '<thead><tr>
<th style="width:30px;"></th>
<th>Статус</th>
<th>Связь</th>
<th>Действия</th>
<th>Зачем</th>
</tr></thead><tbody>';
foreach ($rows as $r) {
$d = $r[0]; $st = $r[1]; $or = $r[2];
$can = ($st[0] === 'warn');
$rel = '<code>'.dbop_h($d['table']).'.'.$d['col'].'</code> → <code>'.dbop_h($d['ref_table']).'.'.$d['ref_col'].'</code>';
$act = 'ON DELETE '.dbop_h($d['on_delete']).', ON UPDATE '.dbop_h($d['on_update']).' '.$or;
echo '<tr>
<td>'.($can ? '<input type="checkbox" name="items[]" value="'.dbop_h($d['id']).'">' : '<input type="checkbox" disabled>') .'</td>
<td><span class="dbop-badge '.$st[0].'">'.dbop_h($st[1]).'</span></td>
<td>'.$rel.'<br><span class="dbop-small">constraint: <code>'.dbop_h($d['name']).'</code></span></td>
<td>'.$act.'</td>
<td>'.dbop_h($d['why']).'</td>
</tr>';
}
echo '</tbody></table>';
echo '<div class="alert alert-warning"><b>Перед применением FK:</b> убедитесь, что у вас есть бэкап. Если таблицы большие, ALTER может идти долго.</div>';
echo '<button class="btn btn-success" onclick="dbop_applySelected(\'apply_fks\', \'#dbop-fk-form\');">Добавить выбранные FOREIGN KEY</button>';
echo '<div id="dbop-fk-result" style="margin-top:10px;"></div>';
echo '</form>';
}
if ($tab === 'docs') {
echo '<h3 style="margin-top:0;">Инструкция</h3>';
echo '<div class="panel panel-default"><div class="panel-heading"><b>1) Что это делает</b></div><div class="panel-body">
<ul>
<li><b>ANALYZE / CHECK / OPTIMIZE / REPAIR</b> - обслуживание таблиц.</li>
<li><b>Индексы</b> - предлагает и добавляет индексы (если их нет).</li>
<li><b>UNIQUE</b> - добавляет уникальные ключи только если нет дублей.</li>
<li><b>FOREIGN KEY</b> - добавляет внешние ключи только если таблицы InnoDB, типы совпадают, нет сирот и есть индекс на дочерней колонке.</li>
</ul>
</div></div>';
echo '<div class="panel panel-default"><div class="panel-heading"><b>2) Как работать - по шагам</b></div><div class="panel-body">
<div class="dbop-steps">
<div class="step">
<h4>Шаг A: Обновить статистику</h4>
<div>Вкладка <b>Таблицы</b> → выделить таблицы → <b>ANALYZE</b> → "Выполнить".</div>
</div>
<div class="step">
<h4>Шаг B: Добавить индексы</h4>
<div>Вкладка <b>Индексы</b> → отметить "можно добавить" → "Добавить выбранные индексы".</div>
</div>
<div class="step">
<h4>Шаг C: UNIQUE (по желанию)</h4>
<div>Вкладка <b>UNIQUE</b> → если есть дубли - сначала исправить дубли, потом добавлять.</div>
</div>
<div class="step">
<h4>Шаг D: FOREIGN KEY (по желанию)</h4>
<div>Вкладка <b>FOREIGN KEY</b> → проверить InnoDB + сироты. Если нужно - вкладка <b>Таблицы</b> → конвертация в InnoDB. После этого применить.</div>
</div>
</div>
</div></div>';
echo '<div class="panel panel-default"><div class="panel-heading"><b>3) Если пункт меню не появился</b></div><div class="panel-body">
<div class="dbop-small">Иногда запись в <code>'.dbop_h(PREFIX).'_admin_sections</code> не добавляется (например, из-за ошибки установки). Нажмите кнопку ниже.</div>
<button class="btn btn-primary" onclick="dbop_toggleSections(); return false;">Ссылка в меню «Сторонние модули» (вкл/выкл)</button>
<div id="dbop-sections-result" style="margin-top:10px;"></div>
</div></div>';
echo '<div class="panel panel-default"><div class="panel-heading"><b>4) Логи</b></div><div class="panel-body">
<div>Лог всех ALTER/команд: <code>/engine/data/dboptimizerpro.log</code></div>
</div></div>';
}
echo ' </div>'; // content
echo ' <div class="'.$boxFooter.'" style="display:flex;justify-content:space-between;align-items:center;">';
echo ' <div class="dbop-small">DBOptimizerPro v'.dbop_h(DBOP_VERSION).'</div>';
echo ' <div class="dbop-small">prefix: <code>'.dbop_h($prefix).'</code></div>';
echo ' </div>';
echo '</div>'; // box
?>
<script>
function dbop_toggleAll(cb, name){
var c = cb.checked;
document.querySelectorAll('input[name="'+name+'"]').forEach(function(x){ x.checked = c; });
}
function dbop_applySelected(action, formSelector){
var form = document.querySelector(formSelector);
var items = [];
form.querySelectorAll('input[name="items[]"]:checked').forEach(function(x){ items.push(x.value); });
if (!items.length) { DLEalert('Ничего не выбрано', 'DBOptimizerPro'); return; }
if (!confirm('Применить выбранные изменения? На больших таблицах это может занять время.')) return;
var fd = new FormData();
fd.append('user_hash', '<?=dbop_h($userHash)?>');
items.forEach(function(v){ fd.append('items[]', v); });
fetch('admin.php?mod=db_optimizer&action='+encodeURIComponent(action)+'&tab=<?=dbop_h($tab)?><?=isset($_GET['show_all']) && (int)$_GET['show_all']===1 ? '&show_all=1' : ''?>', {
method: 'POST',
body: fd,
credentials: 'same-origin'
}).then(r=>r.json()).then(function(data){
var boxId = (action==='apply_indexes') ? 'dbop-index-result' : (action==='apply_unique' ? 'dbop-unique-result' : 'dbop-fk-result');
var el = document.getElementById(boxId);
if (!el) return;
var html = '';
if (data.success) {
html += '<div class="alert alert-success"><b>'+escapeHtml(data.message)+'</b></div>';
if (data.applied && data.applied.length) {
html += '<div class="alert alert-info"><b>Применено:</b><pre style="white-space:pre-wrap;">'+escapeHtml(data.applied.join("\\n"))+'</pre></div>';
}
if (data.skipped && data.skipped.length) {
html += '<div class="alert alert-warning"><b>Пропущено:</b><pre style="white-space:pre-wrap;">'+escapeHtml(JSON.stringify(data.skipped, null, 2))+'</pre></div>';
}
} else {
html += '<div class="alert alert-danger">'+escapeHtml(data.message||'Ошибка')+'</div>';
}
el.innerHTML = html;
}).catch(function(e){
DLEalert('Ошибка: '+e, 'DBOptimizerPro');
});
}
function dbop_runMaintenance(){
var form = document.getElementById('dbop-maint-form');
if (!form) return;
var checked = form.querySelectorAll('input[name="tables[]"]:checked').length;
if (!checked) { DLEalert('Выберите таблицы', 'DBOptimizerPro'); return; }
if (!confirm('Выполнить команду обслуживания на выбранных таблицах?')) return;
var fd = new FormData(form);
fetch('admin.php?mod=db_optimizer&action=maintenance&tab=tables<?=isset($_GET['show_all']) && (int)$_GET['show_all']===1 ? '&show_all=1' : ''?>', {
method: 'POST',
body: fd,
credentials: 'same-origin'
}).then(r=>r.json()).then(function(data){
var el = document.getElementById('dbop-maint-result');
if (!el) return;
if (!data.success) {
el.innerHTML = '<div class="alert alert-danger">'+escapeHtml(data.message||'Ошибка')+'</div>';
return;
}
var rows = data.results || [];
var html = '<div class="alert alert-success"><b>'+escapeHtml(data.message)+'</b></div>';
html += '<div class="alert alert-info"><pre style="white-space:pre-wrap;">'+escapeHtml(JSON.stringify(rows, null, 2))+'</pre></div>';
el.innerHTML = html;
}).catch(function(e){
DLEalert('Ошибка: '+e, 'DBOptimizerPro');
});
}
function dbop_convertEngine(){
var form = document.getElementById('dbop-maint-form');
if (!form) return;
var checked = form.querySelectorAll('input[name="tables[]"]:checked').length;
if (!checked) { DLEalert('Выберите таблицы', 'DBOptimizerPro'); return; }
if (!confirm('Конвертировать выбранные таблицы? Это ALTER TABLE и может быть долгим.')) return;
var fd = new FormData();
fd.append('user_hash', '<?=dbop_h($userHash)?>');
var target = document.getElementById('dbop-engine-target') ? document.getElementById('dbop-engine-target').value : 'INNODB';
fd.append('target', target);
form.querySelectorAll('input[name="tables[]"]:checked').forEach(function(x){ fd.append('tables[]', x.value); });
fetch('admin.php?mod=db_optimizer&action=convert_engine&tab=tables<?=isset($_GET['show_all']) && (int)$_GET['show_all']===1 ? '&show_all=1' : ''?>', {
method: 'POST',
body: fd,
credentials: 'same-origin'
}).then(r=>r.json()).then(function(data){
var el = document.getElementById('dbop-maint-result');
if (!el) return;
if (!data.success) {
el.innerHTML = '<div class="alert alert-danger">'+escapeHtml(data.message||'Ошибка')+'</div>';
return;
}
var html = '<div class="alert alert-success"><b>'+escapeHtml(data.message)+'</b></div>';
if (data.applied && data.applied.length) html += '<div class="alert alert-info"><b>Применено:</b><pre style="white-space:pre-wrap;">'+escapeHtml(data.applied.join("\\n"))+'</pre></div>';
if (data.skipped && data.skipped.length) html += '<div class="alert alert-warning"><b>Пропущено:</b><pre style="white-space:pre-wrap;">'+escapeHtml(JSON.stringify(data.skipped, null, 2))+'</pre></div>';
el.innerHTML = html;
}).catch(function(e){
DLEalert('Ошибка: '+e, 'DBOptimizerPro');
});
}
function dbop_toggleSections(){
fetch('admin.php?mod=db_optimizer&action=sections&tab=docs&user_hash=<?=dbop_h($userHash)?><?=isset($_GET['show_all']) && (int)$_GET['show_all']===1 ? '&show_all=1' : ''?>', {
method: 'GET',
credentials: 'same-origin'
}).then(r=>r.json()).then(function(data){
var el = document.getElementById('dbop-sections-result');
if (!el) { DLEalert(data.message||'Готово', 'DBOptimizerPro'); return; }
el.innerHTML = data.success ? '<div class="alert alert-success">'+escapeHtml(data.message)+'</div>' : '<div class="alert alert-danger">'+escapeHtml(data.message||'Ошибка')+'</div>';
}).catch(function(e){
DLEalert('Ошибка: '+e, 'DBOptimizerPro');
});
}
function escapeHtml(s){
return String(s).replace(/[&<>"']/g, function(m){
return ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]);
});
}
</script>
<?php echofooter(); ?>
]]></replacecode>
<enabled>1</enabled>
</operation>
</file>
</dleplugin>
1. Взять сервер шустрее, более производительный с хорошим процом и большим объемом памяти;Всем привет. Как можно оптимизировать движок DLE если на сайте 40 000 новостей с постерами?
Вот так примерно можно оптимизировать сервер с сайтом на dle и с более большей базой. Подогнать под своё железо Настройка-оптимизация VPS сервера (Стр. 1) - Visavi.netВсем привет. Как можно оптимизировать движок DLE если на сайте 40 000 новостей с постерами?
Вы автор плагина?ok_daa потестите
Код:<?xml version="1.0" encoding="utf-8"?> <dleplugin> <name>DBOptimizerPro</name> <description><![CDATA[ Оптимизация БД DLE: индексы, UNIQUE, FOREIGN KEY, обслуживание таблиц (ANALYZE/CHECK/OPTIMIZE/REPAIR). Как открыть: - Админка → Сторонние модули → DBOptimizerPro - или /admin.php?mod=db_optimizer Важно: - ALTER TABLE/OPTIMIZE может быть долгим и блокировать таблицы. - Перед применением сделайте бэкап. - Лог: /engine/data/dboptimizerpro.log ]]></description> <icon></icon> <version>1.1.0</version> <dleversion>18.0</dleversion> <versioncompare>greater</versioncompare> <upgradeurl></upgradeurl> <filedelete>0</filedelete> <needplugin></needplugin> <mnotice>0</mnotice> <mysqlinstall><![CDATA[ INSERT IGNORE INTO `{prefix}_admin_sections` (`name`, `title`, `descr`, `icon`, `allow_groups`) VALUES ('db_optimizer', 'DBOptimizerPro', 'Оптимизация БД (индексы, UNIQUE, FOREIGN KEY)', '', '1'); ]]></mysqlinstall> <mysqlupgrade><![CDATA[]]></mysqlupgrade> <mysqlenable><![CDATA[]]></mysqlenable> <mysqldisable><![CDATA[]]></mysqldisable> <mysqldelete><![CDATA[ DELETE FROM `{prefix}_admin_sections` WHERE `name`='db_optimizer'; ]]></mysqldelete> <phpinstall><![CDATA[ @unlink(ENGINE_DIR . '/cache/system/adminmenu.php'); ]]></phpinstall> <phpupgrade><![CDATA[]]></phpupgrade> <phpenable><![CDATA[]]></phpenable> <phpdisable><![CDATA[]]></phpdisable> <phpdelete><![CDATA[ @unlink(ENGINE_DIR . '/cache/system/adminmenu.php'); ]]></phpdelete> <notice><![CDATA[]]></notice> <file name="engine/inc/db_optimizer.php"> <operation action="create"> <replacecode><![CDATA[ <?php /** * DBOptimizerPro - модуль админки DLE * Версия: 1.1.0 * * Реализовано: * - Обслуживание таблиц: ANALYZE / CHECK / OPTIMIZE / REPAIR (REPAIR только MyISAM) * - Индексы (обычные) * - UNIQUE (с проверкой дублей) * - FOREIGN KEY (с проверками: InnoDB, типы, индекс, сироты) * * Важно: * - FOREIGN KEY сам по себе не "ускоритель". Чаще ускоряют индексы. * - Любой ALTER TABLE/OPTIMIZE может быть долгим и блокировать таблицы. * - Сделайте бэкап перед применением. */ if (!defined('DATALIFEENGINE') || !defined('LOGGED_IN')) { die('Hacking attempt!'); } if (!isset($member_id) || !is_array($member_id) || (int)$member_id['user_group'] !== 1) { if (function_exists('msg')) { msg('error', 'Доступ запрещён', 'Модуль доступен только администраторам (группа 1).'); } die('Access denied'); } define('DBOP_VERSION', '1.1.0'); // ---------------- helpers ---------------- function dbop_h($v) { return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); } function dbop_bytes($bytes) { $bytes = (float)$bytes; if ($bytes < 1024) return sprintf('%.0f B', $bytes); $u = array('KB','MB','GB','TB','PB'); $i = 0; $bytes /= 1024; while ($bytes >= 1024 && $i < count($u)-1) { $bytes /= 1024; $i++; } return sprintf('%.2f %s', $bytes, $u[$i]); } function dbop_escape_like($s) { return str_replace(array('\\', '%', '_'), array('\\\\', '\\%', '\\_'), (string)$s); } function dbop_require_hash() { global $dle_login_hash; if (!isset($_REQUEST['user_hash']) || $_REQUEST['user_hash'] !== $dle_login_hash) { msg('error', 'Ошибка безопасности', 'Неверный ключ безопасности (user_hash). Обновите страницу и повторите действие.'); } } function dbop_log($line) { $file = ENGINE_DIR . '/data/dboptimizerpro.log'; $ts = date('Y-m-d H:i:s'); @file_put_contents($file, '['.$ts.'] '.$line."\n", FILE_APPEND); } function dbop_is_safe_identifier($s) { return (bool)preg_match('/^[A-Za-z0-9_]+$/', (string)$s); } // --------- DB introspection --------- function dbop_get_tables($prefix, $only_prefix=true) { global $db; $out = array(); $where = "TABLE_SCHEMA = DATABASE()"; if ($only_prefix && $prefix !== '') { // хотим только таблицы DLE: PREFIX_* $like = dbop_escape_like($prefix . '_') . '%'; $where .= " AND TABLE_NAME LIKE '{$like}' ESCAPE '\\\\'"; } $sql = "SELECT TABLE_NAME, ENGINE, TABLE_ROWS, DATA_LENGTH, INDEX_LENGTH, DATA_FREE FROM information_schema.TABLES WHERE {$where} ORDER BY TABLE_NAME"; $db->query($sql); while ($r = $db->get_row()) { $out[$r['TABLE_NAME']] = array( 'table' => $r['TABLE_NAME'], 'engine' => $r['ENGINE'], 'rows' => (int)$r['TABLE_ROWS'], 'data' => (int)$r['DATA_LENGTH'], 'index' => (int)$r['INDEX_LENGTH'], 'free' => (int)$r['DATA_FREE'], ); } $db->free(); return $out; } function dbop_get_columns($table) { global $db; $cols = array(); $db->query("SHOW COLUMNS FROM `{$table}`"); while ($r = $db->get_row()) $cols[] = $r['Field']; $db->free(); return $cols; } function dbop_get_column_types($table) { global $db; $types = array(); $db->query("SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '".$db->safesql($table)."'"); while ($r = $db->get_row()) { $types[$r['COLUMN_NAME']] = array( 'type' => $r['COLUMN_TYPE'], 'null' => $r['IS_NULLABLE'], 'def' => $r['COLUMN_DEFAULT'], ); } $db->free(); return $types; } function dbop_get_indexes($table) { global $db; $idx = array(); $db->query("SHOW INDEX FROM `{$table}`"); while ($r = $db->get_row()) { $name = $r['Key_name']; if (!isset($idx[$name])) { $idx[$name] = array( 'unique' => ((int)$r['Non_unique'] === 0), 'cols' => array(), ); } $seq = (int)$r['Seq_in_index']; $idx[$name]['cols'][$seq] = $r['Column_name']; } $db->free(); foreach ($idx as $k => $v) { ksort($idx[$k]['cols']); $idx[$k]['cols'] = array_values($idx[$k]['cols']); } return $idx; } function dbop_has_index_prefix($indexes, $cols, $need_unique=false) { $n = count($cols); foreach ($indexes as $idx) { if ($need_unique && empty($idx['unique'])) continue; if (count($idx['cols']) < $n) continue; $ok = true; for ($i=0; $i<$n; $i++) { if ((string)$idx['cols'][$i] !== (string)$cols[$i]) { $ok = false; break; } } if ($ok) return true; } return false; } function dbop_fk_exists($table, $col, $ref_table, $ref_col) { global $db; $sql = "SELECT CONSTRAINT_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '".$db->safesql($table)."' AND COLUMN_NAME = '".$db->safesql($col)."' AND REFERENCED_TABLE_NAME = '".$db->safesql($ref_table)."' AND REFERENCED_COLUMN_NAME = '".$db->safesql($ref_col)."' LIMIT 1"; $row = $db->super_query($sql); return !empty($row['CONSTRAINT_NAME']); } function dbop_get_table_engine($tables, $table) { return isset($tables[$table]['engine']) ? strtoupper((string)$tables[$table]['engine']) : ''; } // --------- Definitions (indexes, unique, foreign keys) --------- function dbop_defs($prefix, $tables) { $p = ($prefix !== '') ? ($prefix . '_') : ''; $t_post = $p . 'post'; $t_comm = $p . 'comments'; $t_users = $p . 'users'; $t_extra = $p . 'post_extras'; $t_tags = $p . 'tags'; $has = function($t) use ($tables) { return isset($tables[$t]); }; $defs = array( 'indexes' => array(), 'unique' => array(), 'fks' => array(), ); // ---- обычные индексы ---- if ($has($t_post)) { $defs['indexes'][] = array( 'id' => 'idx_post_approve_date', 'table' => $t_post, 'name' => 'idx_approve_date', 'cols' => array('approve','date'), 'why' => 'Частая выборка: approve=1 + сортировка по date (главная/категории).', ); $defs['indexes'][] = array( 'id' => 'idx_post_autor', 'table' => $t_post, 'name' => 'idx_autor', 'cols' => array('autor'), 'why' => 'Выборки по автору.', ); } if ($has($t_comm)) { $defs['indexes'][] = array( 'id' => 'idx_comments_post_approve_date', 'table' => $t_comm, 'name' => 'idx_post_approve_date', 'cols' => array('post_id','approve','date'), 'why' => 'Вывод комментариев: post_id + approve + сортировка по date.', ); } if ($has($t_tags)) { $defs['indexes'][] = array( 'id' => 'idx_tags_tag', 'table' => $t_tags, 'name' => 'idx_tag', 'cols' => array('tag'), 'why' => 'Поиск по тегу.', ); $defs['indexes'][] = array( 'id' => 'idx_tags_news_id', 'table' => $t_tags, 'name' => 'idx_news_id', 'cols' => array('news_id'), 'why' => 'Связь тегов с новостями.', ); } // ---- UNIQUE ---- if ($has($t_users)) { $defs['unique'][] = array( 'id' => 'uq_users_name', 'table' => $t_users, 'name' => 'uq_name', 'cols' => array('name'), 'why' => 'Имя пользователя обычно должно быть уникальным.', 'dup_sql' => "SELECT `name` AS v, COUNT(*) AS c FROM `{$t_users}` GROUP BY `name` HAVING c>1 LIMIT 50", ); $defs['unique'][] = array( 'id' => 'uq_users_email', 'table' => $t_users, 'name' => 'uq_email', 'cols' => array('email'), 'why' => 'Email часто уникален. Если у вас разрешены одинаковые email - не ставьте.', 'dup_sql' => "SELECT `email` AS v, COUNT(*) AS c FROM `{$t_users}` WHERE `email`<>'' GROUP BY `email` HAVING c>1 LIMIT 50", ); } if ($has($t_extra)) { $defs['unique'][] = array( 'id' => 'uq_post_extras_news_id', 'table' => $t_extra, 'name' => 'uq_news_id', 'cols' => array('news_id'), 'why' => 'Обычно 1 запись post_extras на 1 новость. Ускоряет JOIN по news_id и защищает от дублей.', 'dup_sql' => "SELECT `news_id` AS v, COUNT(*) AS c FROM `{$t_extra}` GROUP BY `news_id` HAVING c>1 LIMIT 50", ); } if ($has($t_tags)) { $defs['unique'][] = array( 'id' => 'uq_tags_news_tag', 'table' => $t_tags, 'name' => 'uq_news_tag', 'cols' => array('news_id','tag'), 'why' => 'Убирает дубли связей (news_id + tag).', 'dup_sql' => "SELECT CONCAT(`news_id`,':',`tag`) AS v, COUNT(*) AS c FROM `{$t_tags}` GROUP BY `news_id`,`tag` HAVING c>1 LIMIT 50", ); } // ---- FOREIGN KEYS ---- if ($has($t_comm) && $has($t_post)) { $defs['fks'][] = array( 'id' => 'fk_comments_post', 'table' => $t_comm, 'col' => 'post_id', 'ref_table' => $t_post, 'ref_col' => 'id', 'name' => 'fk_comments_post', 'on_delete' => 'CASCADE', 'on_update' => 'CASCADE', 'why' => 'Комментарии должны ссылаться на существующую новость. При удалении новости логично удалять комментарии.', 'orphan_sql'=> "SELECT COUNT(*) AS c FROM `{$t_comm}` c LEFT JOIN `{$t_post}` p ON p.`id`=c.`post_id` WHERE c.`post_id`<>0 AND p.`id` IS NULL", ); } if ($has($t_extra) && $has($t_post)) { $defs['fks'][] = array( 'id' => 'fk_post_extras_post', 'table' => $t_extra, 'col' => 'news_id', 'ref_table' => $t_post, 'ref_col' => 'id', 'name' => 'fk_post_extras_post', 'on_delete' => 'CASCADE', 'on_update' => 'CASCADE', 'why' => 'post_extras должен ссылаться на новость. При удалении новости extras должны удаляться.', 'orphan_sql'=> "SELECT COUNT(*) AS c FROM `{$t_extra}` e LEFT JOIN `{$t_post}` p ON p.`id`=e.`news_id` WHERE e.`news_id`<>0 AND p.`id` IS NULL", ); } if ($has($t_tags) && $has($t_post)) { $defs['fks'][] = array( 'id' => 'fk_tags_post', 'table' => $t_tags, 'col' => 'news_id', 'ref_table' => $t_post, 'ref_col' => 'id', 'name' => 'fk_tags_post', 'on_delete' => 'CASCADE', 'on_update' => 'CASCADE', 'why' => 'Связи тегов должны ссылаться на существующую новость.', 'orphan_sql'=> "SELECT COUNT(*) AS c FROM `{$t_tags}` t LEFT JOIN `{$t_post}` p ON p.`id`=t.`news_id` WHERE t.`news_id`<>0 AND p.`id` IS NULL", ); } return $defs; } // ------------- Validation for defs ------------- function dbop_validate_index_def($def, $tables) { $table = $def['table']; if (!isset($tables[$table])) return array(false, 'Таблица не найдена'); $cols = dbop_get_columns($table); $set = array_flip($cols); foreach ($def['cols'] as $c) { if (!isset($set[$c])) return array(false, 'Нет колонки: '.$c); } $idxs = dbop_get_indexes($table); if (dbop_has_index_prefix($idxs, $def['cols'], false)) return array(false, 'Индекс уже есть'); return array(true, ''); } function dbop_validate_unique_def($def, $tables) { $table = $def['table']; if (!isset($tables[$table])) return array(false, 'Таблица не найдена'); $cols = dbop_get_columns($table); $set = array_flip($cols); foreach ($def['cols'] as $c) { if (!isset($set[$c])) return array(false, 'Нет колонки: '.$c); } $idxs = dbop_get_indexes($table); if (dbop_has_index_prefix($idxs, $def['cols'], true)) return array(false, 'UNIQUE уже есть'); global $db; if (!empty($def['dup_sql'])) { $db->query($def['dup_sql']); $row = $db->get_row(); $hasDup = !empty($row); $db->free(); if ($hasDup) return array(false, 'Есть дубли (сначала исправьте)'); } return array(true, ''); } function dbop_validate_fk_def($def, $tables) { global $db; $t = $def['table']; $rt = $def['ref_table']; $c = $def['col']; $rc = $def['ref_col']; if (!isset($tables[$t]) || !isset($tables[$rt])) return array(false, 'Таблица/ссылка не найдена'); $eng1 = dbop_get_table_engine($tables, $t); $eng2 = dbop_get_table_engine($tables, $rt); if ($eng1 !== 'INNODB' || $eng2 !== 'INNODB') return array(false, 'Требуется InnoDB (обе таблицы)'); $cols1 = array_flip(dbop_get_columns($t)); $cols2 = array_flip(dbop_get_columns($rt)); if (!isset($cols1[$c])) return array(false, 'Нет колонки '.$c.' в '.$t); if (!isset($cols2[$rc])) return array(false, 'Нет колонки '.$rc.' в '.$rt); $types1 = dbop_get_column_types($t); $types2 = dbop_get_column_types($rt); $ct1 = isset($types1[$c]['type']) ? strtolower($types1[$c]['type']) : ''; $ct2 = isset($types2[$rc]['type']) ? strtolower($types2[$rc]['type']) : ''; if ($ct1 && $ct2 && $ct1 !== $ct2) { return array(false, 'Типы колонок не совпадают: '.$ct1.' vs '.$ct2); } $idx1 = dbop_get_indexes($t); $hasChildIndex = dbop_has_index_prefix($idx1, array($c), false) || dbop_has_index_prefix($idx1, array($c), true); if (!$hasChildIndex) return array(false, 'Нужен индекс на '.$t.'.'.$c); if (!empty($def['orphan_sql'])) { $row = $db->super_query($def['orphan_sql']); $cnt = isset($row['c']) ? (int)$row['c'] : 0; if ($cnt > 0) return array(false, 'Есть сироты: '.$cnt); } if (dbop_fk_exists($t, $c, $rt, $rc)) return array(false, 'FK уже существует'); return array(true, ''); } // --------- Apply operations --------- function dbop_apply_index($def) { global $db; $table = $def['table']; $name = $def['name']; $cols = $def['cols']; if (!dbop_is_safe_identifier($name)) return array(false, 'Плохое имя индекса'); foreach ($cols as $c) if (!dbop_is_safe_identifier($c)) return array(false, 'Плохая колонка'); $cols_sql = '`' . implode('`,`', $cols) . '`'; $sql = "ALTER TABLE `{$table}` ADD INDEX `{$name}` ({$cols_sql})"; dbop_log('Alter: '.$sql); $db->query($sql); return array(true, $sql); } function dbop_apply_unique($def) { global $db; $table = $def['table']; $name = $def['name']; $cols = $def['cols']; if (!dbop_is_safe_identifier($name)) return array(false, 'Плохое имя индекса'); foreach ($cols as $c) if (!dbop_is_safe_identifier($c)) return array(false, 'Плохая колонка'); $cols_sql = '`' . implode('`,`', $cols) . '`'; $sql = "ALTER TABLE `{$table}` ADD UNIQUE INDEX `{$name}` ({$cols_sql})"; dbop_log('Alter: '.$sql); $db->query($sql); return array(true, $sql); } function dbop_apply_fk($def) { global $db; $t = $def['table']; $c = $def['col']; $rt = $def['ref_table']; $rc = $def['ref_col']; $name = $def['name']; foreach (array($c,$rc,$name) as $x) { if (!dbop_is_safe_identifier($x)) return array(false, 'Плохой идентификатор'); } $onDel = !empty($def['on_delete']) ? strtoupper($def['on_delete']) : 'RESTRICT'; $onUpd = !empty($def['on_update']) ? strtoupper($def['on_update']) : 'RESTRICT'; $allowed = array('RESTRICT','CASCADE','SET NULL','NO ACTION'); if (!in_array($onDel, $allowed, true)) $onDel = 'RESTRICT'; if (!in_array($onUpd, $allowed, true)) $onUpd = 'RESTRICT'; $sql = "ALTER TABLE `{$t}` ADD CONSTRAINT `{$name}` FOREIGN KEY (`{$c}`) REFERENCES `{$rt}`(`{$rc}`) ON DELETE {$onDel} ON UPDATE {$onUpd}"; dbop_log('Alter: '.$sql); $db->query($sql); return array(true, $sql); } function dbop_convert_engine($table, $engine) { global $db; $engine = strtoupper($engine); if (!in_array($engine, array('INNODB','MYISAM'), true)) return array(false, 'Недопустимый engine'); $sql = "ALTER TABLE `{$table}` ENGINE={$engine}"; dbop_log('Alter: '.$sql); $db->query($sql); return array(true, $sql); } // ---------------- Controller ---------------- $prefix = defined('PREFIX') ? PREFIX : ((isset($config['dbprefix']) && $config['dbprefix']) ? $config['dbprefix'] : ''); $only_prefix = true; if (isset($_GET['show_all']) && (int)$_GET['show_all'] === 1) $only_prefix = false; $tables = dbop_get_tables($prefix, $only_prefix); $defs = dbop_defs($prefix, $tables); $tab = isset($_REQUEST['tab']) ? (string)$_REQUEST['tab'] : 'home'; $action = isset($_REQUEST['action']) ? (string)$_REQUEST['action'] : ''; function dbop_json_exit($arr) { header('Content-Type: application/json; charset=utf-8'); echo json_encode($arr, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); exit; } // AJAX actions if ($action) { dbop_require_hash(); if ($action === 'sections') { // добавить/убрать из "Сторонние модули" (на случай, если запись не создалась при установке) global $db; $row = $db->super_query("SELECT id FROM " . PREFIX . "_admin_sections WHERE name='db_optimizer'"); if ($row) { $db->query("DELETE FROM " . PREFIX . "_admin_sections WHERE name='db_optimizer'"); @unlink(ENGINE_DIR . '/cache/system/adminmenu.php'); dbop_json_exit(array('success'=>true,'message'=>'Ссылка убрана из меню «Сторонние модули»')); } else { $db->query("INSERT IGNORE INTO " . PREFIX . "_admin_sections (name, title, descr, icon, allow_groups) VALUES ('db_optimizer', 'DBOptimizerPro', 'Оптимизация БД (индексы, UNIQUE, FOREIGN KEY)', '', '1')"); @unlink(ENGINE_DIR . '/cache/system/adminmenu.php'); dbop_json_exit(array('success'=>true,'message'=>'Ссылка добавлена в меню «Сторонние модули»')); } } if ($action === 'maintenance') { $cmd = strtolower((string)($_POST['cmd'] ?? 'analyze')); $allowed_cmds = array('analyze'=>'ANALYZE','check'=>'CHECK','optimize'=>'OPTIMIZE','repair'=>'REPAIR'); if (!isset($allowed_cmds[$cmd])) dbop_json_exit(array('success'=>false,'message'=>'Неизвестная команда')); $sel = isset($_POST['tables']) && is_array($_POST['tables']) ? $_POST['tables'] : array(); $sel = array_values(array_unique(array_map('strval',$sel))); $safe = array(); foreach ($sel as $t) if (isset($tables[$t])) $safe[] = $t; if (!$safe) dbop_json_exit(array('success'=>false,'message'=>'Не выбраны таблицы')); $results = array(); foreach ($safe as $tname) { $sqlCmd = $allowed_cmds[$cmd]; if ($sqlCmd === 'REPAIR') { $eng = strtoupper((string)$tables[$tname]['engine']); if ($eng !== 'MYISAM') { $results[] = array('table'=>$tname,'status'=>'SKIP','msg'=>'REPAIR только для MyISAM'); continue; } } $q = "{$sqlCmd} TABLE `{$tname}`"; dbop_log('Run: '.$q); $db->query($q); while ($r = $db->get_row()) { $results[] = array('table'=>$r['Table'],'type'=>$r['Msg_type'],'msg'=>$r['Msg_text']); } $db->free(); } dbop_json_exit(array('success'=>true,'message'=>'Готово','results'=>$results)); } if ($action === 'apply_indexes' || $action === 'apply_unique' || $action === 'apply_fks') { $items = isset($_POST['items']) && is_array($_POST['items']) ? $_POST['items'] : array(); $items = array_values(array_unique(array_map('strval',$items))); if (!$items) dbop_json_exit(array('success'=>false,'message'=>'Ничего не выбрано')); $kind = ($action === 'apply_indexes') ? 'indexes' : (($action === 'apply_unique') ? 'unique' : 'fks'); $defsList = $defs[$kind]; $map = array(); foreach ($defsList as $d) $map[$d['id']] = $d; $applied = array(); $skipped = array(); foreach ($items as $id) { if (!isset($map[$id])) { $skipped[] = array('id'=>$id,'reason'=>'unknown'); continue; } $d = $map[$id]; if ($kind === 'indexes') { list($ok,$why) = dbop_validate_index_def($d, $tables); if (!$ok) { $skipped[] = array('id'=>$id,'reason'=>$why); continue; } list($ok2,$sql) = dbop_apply_index($d); if (!$ok2) { $skipped[] = array('id'=>$id,'reason'=>$sql); continue; } $applied[] = $sql; } if ($kind === 'unique') { list($ok,$why) = dbop_validate_unique_def($d, $tables); if (!$ok) { $skipped[] = array('id'=>$id,'reason'=>$why); continue; } list($ok2,$sql) = dbop_apply_unique($d); if (!$ok2) { $skipped[] = array('id'=>$id,'reason'=>$sql); continue; } $applied[] = $sql; } if ($kind === 'fks') { list($ok,$why) = dbop_validate_fk_def($d, $tables); if (!$ok) { $skipped[] = array('id'=>$id,'reason'=>$why); continue; } list($ok2,$sql) = dbop_apply_fk($d); if (!$ok2) { $skipped[] = array('id'=>$id,'reason'=>$sql); continue; } $applied[] = $sql; } } dbop_json_exit(array( 'success'=>true, 'message'=>'Операция завершена', 'applied'=>$applied, 'skipped'=>$skipped, )); } if ($action === 'convert_engine') { $items = isset($_POST['tables']) && is_array($_POST['tables']) ? $_POST['tables'] : array(); $items = array_values(array_unique(array_map('strval',$items))); $target = strtoupper((string)($_POST['target'] ?? 'INNODB')); if (!in_array($target, array('INNODB','MYISAM'), true)) $target = 'INNODB'; if (!$items) dbop_json_exit(array('success'=>false,'message'=>'Не выбраны таблицы')); $applied = array(); $skipped = array(); foreach ($items as $t) { if (!isset($tables[$t])) { $skipped[] = array('table'=>$t,'reason'=>'unknown'); continue; } $cur = strtoupper((string)$tables[$t]['engine']); if ($cur === $target) { $skipped[] = array('table'=>$t,'reason'=>'уже '.$target); continue; } list($ok,$sql) = dbop_convert_engine($t, $target); if (!$ok) { $skipped[] = array('table'=>$t,'reason'=>$sql); continue; } $applied[] = $sql; } dbop_json_exit(array('success'=>true,'message'=>'Готово','applied'=>$applied,'skipped'=>$skipped)); } dbop_json_exit(array('success'=>false,'message'=>'Unknown action')); } // ---------------- UI (Admin) ---------------- $__name = 'DBOptimizerPro'; $__descr = 'Оптимизация БД (индексы, UNIQUE, FOREIGN KEY)'; echoheader($__name, $__descr); $adminUrl = $config['admin_path'] ?? 'admin.php'; $userHash = $dle_login_hash; $box = ($config['version_id'] >= 12) ? 'panel' : 'box'; $boxHeader = ($config['version_id'] >= 12) ? 'panel-heading' : 'box-header'; $boxContent = 'box-content'; $boxFooter = ($config['version_id'] >= 12) ? 'panel-footer' : 'box-footer'; $tabsClass = ($config['version_id'] >= 12) ? 'nav-tabs-solid' : 'nav-tabs-left'; function dbop_tab_link($tab, $label, $icon='') { $u = 'admin.php?mod=db_optimizer&tab='.$tab.(isset($_GET['show_all']) && (int)$_GET['show_all']===1 ? '&show_all=1' : ''); $i = $icon ? '<i class="'.$icon.'"></i> ' : ''; $active = ((isset($_REQUEST['tab']) && $_REQUEST['tab']===$tab) || (!isset($_REQUEST['tab']) && $tab==='home')) ? ' class="active"' : ''; return '<li'.$active.'><a href="'.$u.'">'.$i.dbop_h($label).'</a></li>'; } echo '<style> /* читабельные табы (у вас были "белое на белом") */ .dbop-wrap .nav-tabs>li>a{color:#333 !important; background:#f6f7f9; border:1px solid #e2e5ea; margin-right:6px; border-bottom:none;} .dbop-wrap .nav-tabs>li.active>a{background:#ffffff !important; color:#000 !important; font-weight:600;} .dbop-wrap .nav-tabs{border-bottom:1px solid #e2e5ea;} .dbop-steps .step{border:1px solid #e5e5e5;border-radius:10px;padding:12px;margin:10px 0;background:#fff;} .dbop-steps .step h4{margin:0 0 6px 0;} .dbop-small{color:#666;font-size:12px;} .dbop-badge{display:inline-block;padding:3px 8px;border-radius:999px;font-size:12px;border:1px solid #ddd;background:#fafafa;} .dbop-badge.ok{background:#e8fff0;border-color:#bfe8cd;} .dbop-badge.bad{background:#fff0f0;border-color:#f1c1c1;} .dbop-badge.warn{background:#fff8e6;border-color:#f2d49a;} .dbop-table code{font-size:12px;} </style>'; echo '<div class="dbop-wrap '.$box.'">'; echo ' <div class="'.$boxHeader.'">'; echo ' <ul class="nav nav-tabs '.$tabsClass.'">'; echo dbop_tab_link('home', 'Быстрый старт', 'icon-home'); echo dbop_tab_link('tables', 'Таблицы', 'icon-database'); echo dbop_tab_link('indexes', 'Индексы', 'icon-list'); echo dbop_tab_link('unique', 'UNIQUE', 'icon-check'); echo dbop_tab_link('fks', 'FOREIGN KEY', 'icon-link'); echo dbop_tab_link('docs', 'Инструкция', 'icon-book'); echo ' </ul>'; echo ' </div>'; echo ' <div class="'.$boxContent.'">'; echo '<div class="alert alert-warning" style="margin-bottom:10px;"> <b>DBOptimizerPro v'.dbop_h(DBOP_VERSION).'</b><br> Изменения структуры (ALTER TABLE, OPTIMIZE) могут быть долгими и блокировать таблицы. Перед применением сделайте бэкап. Лог: <code>/engine/data/dboptimizerpro.log</code> </div>'; if ($prefix === '') { echo '<div class="alert alert-danger"><b>Проблема:</b> не удалось определить PREFIX. Модуль не знает, какие таблицы считать DLE.</div>'; } else { echo '<div class="alert alert-info" style="margin-bottom:10px;"> <b>Фильтр:</b> сейчас показаны '.($only_prefix ? 'только таблицы DLE (<code>'.dbop_h($prefix).'_*</code>)' : 'все таблицы текущей БД').'. <a href="admin.php?mod=db_optimizer&tab='.dbop_h($tab).($only_prefix ? '&show_all=1' : '').'">'.($only_prefix ? 'Показать все' : 'Показать только DLE').'</a> </div>'; } if ($tab === 'home') { $totalData = 0; $totalIdx = 0; foreach ($tables as $t) { $totalData += $t['data']; $totalIdx += $t['index']; } echo '<div class="dbop-steps">'; echo '<div class="step"> <h4>Сценарий 1. Безопасно и быстро (рекомендуется)</h4> <div class="dbop-small">Обновить статистику БД и добавить обычные индексы (без UNIQUE и FOREIGN KEY).</div> <ol style="margin:8px 0 0 18px;"> <li>Вкладка <b>Таблицы</b> → выделите таблицы → <b>ANALYZE</b> → "Выполнить".</li> <li>Вкладка <b>Индексы</b> → отметьте "можно добавить" → "Добавить выбранные индексы".</li> </ol> </div>'; echo '<div class="step"> <h4>Сценарий 2. Уникальность данных (UNIQUE)</h4> <div class="dbop-small">UNIQUE не применится, если в таблице есть дубли - модуль покажет примеры.</div> <ol style="margin:8px 0 0 18px;"> <li>Вкладка <b>UNIQUE</b> → проверьте статус.</li> <li>Если есть дубли - сначала исправьте, потом применяйте.</li> </ol> </div>'; echo '<div class="step"> <h4>Сценарий 3. FOREIGN KEY (осторожно)</h4> <div class="dbop-small">FOREIGN KEY повышает целостность. Требует InnoDB и отсутствие "сирот".</div> <ol style="margin:8px 0 0 18px;"> <li>Вкладка <b>FOREIGN KEY</b> → убедитесь, что всё "можно добавить".</li> <li>Если таблицы не InnoDB - вкладка <b>Таблицы</b> → конвертация в InnoDB.</li> </ol> </div>'; echo '</div>'; echo '<hr>'; echo '<div class="row"><div class="col-md-6"> <div class="alert alert-info"> <b>Сводка</b><br> Таблиц: <b>'.count($tables).'</b><br> Data: <b>'.dbop_h(dbop_bytes($totalData)).'</b><br> Index: <b>'.dbop_h(dbop_bytes($totalIdx)).'</b><br> Total: <b>'.dbop_h(dbop_bytes($totalData+$totalIdx)).'</b> </div> </div><div class="col-md-6"> <div class="alert alert-default"> <b>Что реально ускоряет чаще всего</b><br> 1) индексы под реальные WHERE/ORDER BY<br> 2) кэш (Redis/Memcached), PHP-FPM, настройки MySQL<br> 3) анализ slow query log и правка запросов </div> </div></div>'; } if ($tab === 'tables') { echo '<h3 style="margin-top:0;">Таблицы и обслуживание</h3>'; echo '<div class="dbop-small">Команды: <b>ANALYZE</b> (рекомендуется), <b>CHECK</b>, <b>OPTIMIZE</b> (осторожно), <b>REPAIR</b> (только MyISAM).</div>'; echo '<div class="alert alert-warning" style="margin-top:10px;"> <b>OPTIMIZE</b> на InnoDB часто равен перестройке таблицы и может быть очень долгим. Используйте после массовых удалений или если видите реальную фрагментацию/раздувание. </div>'; echo '<form id="dbop-maint-form" onsubmit="return false;">'; echo '<input type="hidden" name="user_hash" value="'.dbop_h($userHash).'">'; echo '<div style="margin:8px 0;">'; echo '<select name="cmd" class="uniform"> <option value="analyze">ANALYZE (рекомендуется)</option> <option value="check">CHECK</option> <option value="optimize">OPTIMIZE (осторожно)</option> <option value="repair">REPAIR (только MyISAM)</option> </select> '; echo '<button class="btn btn-primary" onclick="dbop_runMaintenance();">Выполнить команду на выбранных таблицах</button>'; echo '</div>'; echo '<table class="table table-normal table-hover dbop-table" style="width:100%;">'; echo '<thead><tr> <th style="width:30px;"><input type="checkbox" onclick="dbop_toggleAll(this, \'tables[]\');"></th> <th>Таблица</th> <th>Engine</th> <th style="text-align:right;">Rows*</th> <th style="text-align:right;">Data</th> <th style="text-align:right;">Index</th> <th style="text-align:right;">Total</th> <th style="text-align:right;">Free</th> </tr></thead><tbody>'; foreach ($tables as $t) { $total = $t['data'] + $t['index']; $eng = strtoupper((string)$t['engine']); $badge = ($eng==='INNODB') ? 'ok' : (($eng==='MYISAM') ? 'warn' : 'warn'); echo '<tr> <td><input type="checkbox" name="tables[]" value="'.dbop_h($t['table']).'"></td> <td><code>'.dbop_h($t['table']).'</code></td> <td><span class="dbop-badge '.$badge.'">'.dbop_h($eng).'</span></td> <td style="text-align:right;">'.dbop_h($t['rows']).'</td> <td style="text-align:right;">'.dbop_h(dbop_bytes($t['data'])).'</td> <td style="text-align:right;">'.dbop_h(dbop_bytes($t['index'])).'</td> <td style="text-align:right;"><b>'.dbop_h(dbop_bytes($total)).'</b></td> <td style="text-align:right;">'.dbop_h(dbop_bytes($t['free'])).'</td> </tr>'; } echo '</tbody></table>'; echo '<div class="dbop-small">* TABLE_ROWS в InnoDB - оценка, не точное значение.</div>'; echo '</form>'; echo '<hr><h4>Переключить движок таблиц (для FOREIGN KEY нужен InnoDB)</h4>'; echo '<div class="dbop-small">Конвертация - это ALTER TABLE и может быть долгой. Делайте в непиковое время.</div>'; echo '<div style="margin:8px 0;">'; echo '<select id="dbop-engine-target" class="uniform"><option value="INNODB">INNODB</option><option value="MYISAM">MYISAM</option></select> '; echo '<button class="btn btn-warning" onclick="dbop_convertEngine();">Конвертировать выбранные таблицы</button>'; echo '</div>'; echo '<div id="dbop-maint-result"></div>'; } if ($tab === 'indexes') { echo '<h3 style="margin-top:0;">Индексы (обычные)</h3>'; echo '<div class="dbop-small">Индексы обычно безопаснее, чем UNIQUE/FK. Они ускоряют SELECT, но увеличивают размер БД и могут немного замедлить INSERT/UPDATE.</div>'; if (!$defs['indexes']) { echo '<div class="alert alert-warning">Список рекомендуемых индексов пуст. Обычно это значит, что не найдены таблицы <code>'.dbop_h($prefix).'_post</code>/<code>'.dbop_h($prefix).'_comments</code> и т.д.</div>'; } $rows = array(); foreach ($defs['indexes'] as $d) { $table = $d['table']; $idxs = isset($tables[$table]) ? dbop_get_indexes($table) : array(); list($ok,$reason) = dbop_validate_index_def($d, $tables); $exists = isset($tables[$table]) ? dbop_has_index_prefix($idxs, $d['cols'], false) : false; $status = $exists ? array('ok','уже есть') : ($ok ? array('warn','можно добавить') : array('bad',$reason)); $rows[] = array($d, $status); } echo '<form id="dbop-index-form" onsubmit="return false;">'; echo '<input type="hidden" name="user_hash" value="'.dbop_h($userHash).'">'; echo '<table class="table table-normal table-hover dbop-table" style="width:100%;">'; echo '<thead><tr> <th style="width:30px;"></th> <th>Статус</th> <th>Таблица</th> <th>Индекс</th> <th>Колонки</th> <th>Зачем</th> </tr></thead><tbody>'; foreach ($rows as $r) { $d = $r[0]; $st = $r[1]; $can = ($st[0] === 'warn'); echo '<tr> <td>'.($can ? '<input type="checkbox" name="items[]" value="'.dbop_h($d['id']).'">' : '<input type="checkbox" disabled>') .'</td> <td><span class="dbop-badge '.$st[0].'">'.dbop_h($st[1]).'</span></td> <td><code>'.dbop_h($d['table']).'</code></td> <td><code>'.dbop_h($d['name']).'</code></td> <td><code>'.dbop_h(implode(', ', $d['cols'])).'</code></td> <td>'.dbop_h($d['why']).'</td> </tr>'; } echo '</tbody></table>'; echo '<button class="btn btn-success" onclick="dbop_applySelected(\'apply_indexes\', \'#dbop-index-form\');">Добавить выбранные индексы</button>'; echo '<div id="dbop-index-result" style="margin-top:10px;"></div>'; echo '</form>'; } if ($tab === 'unique') { echo '<h3 style="margin-top:0;">UNIQUE ключи (с проверкой дублей)</h3>'; echo '<div class="dbop-small">UNIQUE не применится, если уже есть дубликаты. Плагин покажет примеры дублей.</div>'; if (!$defs['unique']) { echo '<div class="alert alert-warning">Список UNIQUE пуст (скорее всего, таблицы DLE не найдены по префиксу).</div>'; } $rows = array(); foreach ($defs['unique'] as $d) { $table = $d['table']; $idxs = isset($tables[$table]) ? dbop_get_indexes($table) : array(); $exists = isset($tables[$table]) ? dbop_has_index_prefix($idxs, $d['cols'], true) : false; list($ok,$reason) = dbop_validate_unique_def($d, $tables); $status = $exists ? array('ok','уже есть') : ($ok ? array('warn','можно добавить') : array('bad',$reason)); $rows[] = array($d,$status); } echo '<form id="dbop-unique-form" onsubmit="return false;">'; echo '<input type="hidden" name="user_hash" value="'.dbop_h($userHash).'">'; echo '<table class="table table-normal table-hover dbop-table" style="width:100%;">'; echo '<thead><tr> <th style="width:30px;"></th> <th>Статус</th> <th>Таблица</th> <th>UNIQUE</th> <th>Колонки</th> <th>Комментарий</th> <th>Дубли (если есть)</th> </tr></thead><tbody>'; foreach ($rows as $r) { $d = $r[0]; $st = $r[1]; $can = ($st[0] === 'warn'); $dupPreview = ''; if ($st[0] === 'bad' && strpos($st[1], 'дубли') !== false && !empty($d['dup_sql'])) { $db->query($d['dup_sql']); $i = 0; $list = array(); while (($row = $db->get_row()) && $i < 5) { $list[] = $row['v'].' (x'.$row['c'].')'; $i++; } $db->free(); if ($list) $dupPreview = implode('<br>', array_map('dbop_h', $list)); } echo '<tr> <td>'.($can ? '<input type="checkbox" name="items[]" value="'.dbop_h($d['id']).'">' : '<input type="checkbox" disabled>') .'</td> <td><span class="dbop-badge '.$st[0].'">'.dbop_h($st[1]).'</span></td> <td><code>'.dbop_h($d['table']).'</code></td> <td><code>'.dbop_h($d['name']).'</code></td> <td><code>'.dbop_h(implode(', ', $d['cols'])).'</code></td> <td>'.dbop_h($d['why']).'</td> <td>'.($dupPreview ?: '<span class="dbop-small">-</span>').'</td> </tr>'; } echo '</tbody></table>'; echo '<button class="btn btn-success" onclick="dbop_applySelected(\'apply_unique\', \'#dbop-unique-form\');">Добавить выбранные UNIQUE</button>'; echo '<div id="dbop-unique-result" style="margin-top:10px;"></div>'; echo '</form>'; } if ($tab === 'fks') { echo '<h3 style="margin-top:0;">FOREIGN KEY (внешние ключи)</h3>'; echo '<div class="alert alert-info"> <b>Что будет сделано:</b> добавятся связи между связанными таблицами DLE (например comments.post_id → post.id).<br> <b>Почему это может не примениться:</b> не InnoDB, разные типы колонок, нет индекса на дочерней колонке, есть "сироты". </div>'; if (!$defs['fks']) { echo '<div class="alert alert-warning">Список FOREIGN KEY пуст (скорее всего, таблицы DLE не найдены по префиксу).</div>'; } $needInno = array(); foreach ($defs['fks'] as $d) { $needInno[$d['table']] = true; $needInno[$d['ref_table']] = true; } $notInno = array(); foreach (array_keys($needInno) as $t) { if (isset($tables[$t])) { $eng = strtoupper((string)$tables[$t]['engine']); if ($eng !== 'INNODB') $notInno[] = $t.' ('.$eng.')'; } } if ($notInno) { echo '<div class="alert alert-warning"><b>Сейчас не InnoDB:</b><br>'.implode('<br>', array_map('dbop_h', $notInno)).'<br> Сначала переведите эти таблицы в InnoDB (вкладка "Таблицы").</div>'; } elseif ($defs['fks']) { echo '<div class="alert alert-success">Нужные таблицы уже InnoDB.</div>'; } $rows = array(); foreach ($defs['fks'] as $d) { list($ok,$reason) = dbop_validate_fk_def($d, $tables); $exists = dbop_fk_exists($d['table'], $d['col'], $d['ref_table'], $d['ref_col']); $status = $exists ? array('ok','уже есть') : ($ok ? array('warn','можно добавить') : array('bad',$reason)); $orphanInfo = ''; if (!empty($d['orphan_sql'])) { $row = $db->super_query($d['orphan_sql']); $cnt = isset($row['c']) ? (int)$row['c'] : 0; $orphanInfo = ($cnt>0) ? '<span class="dbop-badge bad">сироты: '.dbop_h($cnt).'</span>' : '<span class="dbop-badge ok">сирот нет</span>'; } $rows[] = array($d,$status,$orphanInfo); } echo '<form id="dbop-fk-form" onsubmit="return false;">'; echo '<input type="hidden" name="user_hash" value="'.dbop_h($userHash).'">'; echo '<table class="table table-normal table-hover dbop-table" style="width:100%;">'; echo '<thead><tr> <th style="width:30px;"></th> <th>Статус</th> <th>Связь</th> <th>Действия</th> <th>Зачем</th> </tr></thead><tbody>'; foreach ($rows as $r) { $d = $r[0]; $st = $r[1]; $or = $r[2]; $can = ($st[0] === 'warn'); $rel = '<code>'.dbop_h($d['table']).'.'.$d['col'].'</code> → <code>'.dbop_h($d['ref_table']).'.'.$d['ref_col'].'</code>'; $act = 'ON DELETE '.dbop_h($d['on_delete']).', ON UPDATE '.dbop_h($d['on_update']).' '.$or; echo '<tr> <td>'.($can ? '<input type="checkbox" name="items[]" value="'.dbop_h($d['id']).'">' : '<input type="checkbox" disabled>') .'</td> <td><span class="dbop-badge '.$st[0].'">'.dbop_h($st[1]).'</span></td> <td>'.$rel.'<br><span class="dbop-small">constraint: <code>'.dbop_h($d['name']).'</code></span></td> <td>'.$act.'</td> <td>'.dbop_h($d['why']).'</td> </tr>'; } echo '</tbody></table>'; echo '<div class="alert alert-warning"><b>Перед применением FK:</b> убедитесь, что у вас есть бэкап. Если таблицы большие, ALTER может идти долго.</div>'; echo '<button class="btn btn-success" onclick="dbop_applySelected(\'apply_fks\', \'#dbop-fk-form\');">Добавить выбранные FOREIGN KEY</button>'; echo '<div id="dbop-fk-result" style="margin-top:10px;"></div>'; echo '</form>'; } if ($tab === 'docs') { echo '<h3 style="margin-top:0;">Инструкция</h3>'; echo '<div class="panel panel-default"><div class="panel-heading"><b>1) Что это делает</b></div><div class="panel-body"> <ul> <li><b>ANALYZE / CHECK / OPTIMIZE / REPAIR</b> - обслуживание таблиц.</li> <li><b>Индексы</b> - предлагает и добавляет индексы (если их нет).</li> <li><b>UNIQUE</b> - добавляет уникальные ключи только если нет дублей.</li> <li><b>FOREIGN KEY</b> - добавляет внешние ключи только если таблицы InnoDB, типы совпадают, нет сирот и есть индекс на дочерней колонке.</li> </ul> </div></div>'; echo '<div class="panel panel-default"><div class="panel-heading"><b>2) Как работать - по шагам</b></div><div class="panel-body"> <div class="dbop-steps"> <div class="step"> <h4>Шаг A: Обновить статистику</h4> <div>Вкладка <b>Таблицы</b> → выделить таблицы → <b>ANALYZE</b> → "Выполнить".</div> </div> <div class="step"> <h4>Шаг B: Добавить индексы</h4> <div>Вкладка <b>Индексы</b> → отметить "можно добавить" → "Добавить выбранные индексы".</div> </div> <div class="step"> <h4>Шаг C: UNIQUE (по желанию)</h4> <div>Вкладка <b>UNIQUE</b> → если есть дубли - сначала исправить дубли, потом добавлять.</div> </div> <div class="step"> <h4>Шаг D: FOREIGN KEY (по желанию)</h4> <div>Вкладка <b>FOREIGN KEY</b> → проверить InnoDB + сироты. Если нужно - вкладка <b>Таблицы</b> → конвертация в InnoDB. После этого применить.</div> </div> </div> </div></div>'; echo '<div class="panel panel-default"><div class="panel-heading"><b>3) Если пункт меню не появился</b></div><div class="panel-body"> <div class="dbop-small">Иногда запись в <code>'.dbop_h(PREFIX).'_admin_sections</code> не добавляется (например, из-за ошибки установки). Нажмите кнопку ниже.</div> <button class="btn btn-primary" onclick="dbop_toggleSections(); return false;">Ссылка в меню «Сторонние модули» (вкл/выкл)</button> <div id="dbop-sections-result" style="margin-top:10px;"></div> </div></div>'; echo '<div class="panel panel-default"><div class="panel-heading"><b>4) Логи</b></div><div class="panel-body"> <div>Лог всех ALTER/команд: <code>/engine/data/dboptimizerpro.log</code></div> </div></div>'; } echo ' </div>'; // content echo ' <div class="'.$boxFooter.'" style="display:flex;justify-content:space-between;align-items:center;">'; echo ' <div class="dbop-small">DBOptimizerPro v'.dbop_h(DBOP_VERSION).'</div>'; echo ' <div class="dbop-small">prefix: <code>'.dbop_h($prefix).'</code></div>'; echo ' </div>'; echo '</div>'; // box ?> <script> function dbop_toggleAll(cb, name){ var c = cb.checked; document.querySelectorAll('input[name="'+name+'"]').forEach(function(x){ x.checked = c; }); } function dbop_applySelected(action, formSelector){ var form = document.querySelector(formSelector); var items = []; form.querySelectorAll('input[name="items[]"]:checked').forEach(function(x){ items.push(x.value); }); if (!items.length) { DLEalert('Ничего не выбрано', 'DBOptimizerPro'); return; } if (!confirm('Применить выбранные изменения? На больших таблицах это может занять время.')) return; var fd = new FormData(); fd.append('user_hash', '<?=dbop_h($userHash)?>'); items.forEach(function(v){ fd.append('items[]', v); }); fetch('admin.php?mod=db_optimizer&action='+encodeURIComponent(action)+'&tab=<?=dbop_h($tab)?><?=isset($_GET['show_all']) && (int)$_GET['show_all']===1 ? '&show_all=1' : ''?>', { method: 'POST', body: fd, credentials: 'same-origin' }).then(r=>r.json()).then(function(data){ var boxId = (action==='apply_indexes') ? 'dbop-index-result' : (action==='apply_unique' ? 'dbop-unique-result' : 'dbop-fk-result'); var el = document.getElementById(boxId); if (!el) return; var html = ''; if (data.success) { html += '<div class="alert alert-success"><b>'+escapeHtml(data.message)+'</b></div>'; if (data.applied && data.applied.length) { html += '<div class="alert alert-info"><b>Применено:</b><pre style="white-space:pre-wrap;">'+escapeHtml(data.applied.join("\\n"))+'</pre></div>'; } if (data.skipped && data.skipped.length) { html += '<div class="alert alert-warning"><b>Пропущено:</b><pre style="white-space:pre-wrap;">'+escapeHtml(JSON.stringify(data.skipped, null, 2))+'</pre></div>'; } } else { html += '<div class="alert alert-danger">'+escapeHtml(data.message||'Ошибка')+'</div>'; } el.innerHTML = html; }).catch(function(e){ DLEalert('Ошибка: '+e, 'DBOptimizerPro'); }); } function dbop_runMaintenance(){ var form = document.getElementById('dbop-maint-form'); if (!form) return; var checked = form.querySelectorAll('input[name="tables[]"]:checked').length; if (!checked) { DLEalert('Выберите таблицы', 'DBOptimizerPro'); return; } if (!confirm('Выполнить команду обслуживания на выбранных таблицах?')) return; var fd = new FormData(form); fetch('admin.php?mod=db_optimizer&action=maintenance&tab=tables<?=isset($_GET['show_all']) && (int)$_GET['show_all']===1 ? '&show_all=1' : ''?>', { method: 'POST', body: fd, credentials: 'same-origin' }).then(r=>r.json()).then(function(data){ var el = document.getElementById('dbop-maint-result'); if (!el) return; if (!data.success) { el.innerHTML = '<div class="alert alert-danger">'+escapeHtml(data.message||'Ошибка')+'</div>'; return; } var rows = data.results || []; var html = '<div class="alert alert-success"><b>'+escapeHtml(data.message)+'</b></div>'; html += '<div class="alert alert-info"><pre style="white-space:pre-wrap;">'+escapeHtml(JSON.stringify(rows, null, 2))+'</pre></div>'; el.innerHTML = html; }).catch(function(e){ DLEalert('Ошибка: '+e, 'DBOptimizerPro'); }); } function dbop_convertEngine(){ var form = document.getElementById('dbop-maint-form'); if (!form) return; var checked = form.querySelectorAll('input[name="tables[]"]:checked').length; if (!checked) { DLEalert('Выберите таблицы', 'DBOptimizerPro'); return; } if (!confirm('Конвертировать выбранные таблицы? Это ALTER TABLE и может быть долгим.')) return; var fd = new FormData(); fd.append('user_hash', '<?=dbop_h($userHash)?>'); var target = document.getElementById('dbop-engine-target') ? document.getElementById('dbop-engine-target').value : 'INNODB'; fd.append('target', target); form.querySelectorAll('input[name="tables[]"]:checked').forEach(function(x){ fd.append('tables[]', x.value); }); fetch('admin.php?mod=db_optimizer&action=convert_engine&tab=tables<?=isset($_GET['show_all']) && (int)$_GET['show_all']===1 ? '&show_all=1' : ''?>', { method: 'POST', body: fd, credentials: 'same-origin' }).then(r=>r.json()).then(function(data){ var el = document.getElementById('dbop-maint-result'); if (!el) return; if (!data.success) { el.innerHTML = '<div class="alert alert-danger">'+escapeHtml(data.message||'Ошибка')+'</div>'; return; } var html = '<div class="alert alert-success"><b>'+escapeHtml(data.message)+'</b></div>'; if (data.applied && data.applied.length) html += '<div class="alert alert-info"><b>Применено:</b><pre style="white-space:pre-wrap;">'+escapeHtml(data.applied.join("\\n"))+'</pre></div>'; if (data.skipped && data.skipped.length) html += '<div class="alert alert-warning"><b>Пропущено:</b><pre style="white-space:pre-wrap;">'+escapeHtml(JSON.stringify(data.skipped, null, 2))+'</pre></div>'; el.innerHTML = html; }).catch(function(e){ DLEalert('Ошибка: '+e, 'DBOptimizerPro'); }); } function dbop_toggleSections(){ fetch('admin.php?mod=db_optimizer&action=sections&tab=docs&user_hash=<?=dbop_h($userHash)?><?=isset($_GET['show_all']) && (int)$_GET['show_all']===1 ? '&show_all=1' : ''?>', { method: 'GET', credentials: 'same-origin' }).then(r=>r.json()).then(function(data){ var el = document.getElementById('dbop-sections-result'); if (!el) { DLEalert(data.message||'Готово', 'DBOptimizerPro'); return; } el.innerHTML = data.success ? '<div class="alert alert-success">'+escapeHtml(data.message)+'</div>' : '<div class="alert alert-danger">'+escapeHtml(data.message||'Ошибка')+'</div>'; }).catch(function(e){ DLEalert('Ошибка: '+e, 'DBOptimizerPro'); }); } function escapeHtml(s){ return String(s).replace(/[&<>"']/g, function(m){ return ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]); }); } </script> <?php echofooter(); ?> ]]></replacecode> <enabled>1</enabled> </operation> </file> </dleplugin>
да и нет (с приминением ии)Вы автор плагина?