database.png
· 245 KiB · Image (PNG)
原始文件
gistfile1.txt
· 38 KiB · Text
原始文件
<?php
/**
* Plugin Name: Database Optimizer Pro
* Plugin URI: https://fedia.eu
* Description: Професионален инструмент за оптимизация на WordPress база данни с модерен интерфейс
* Version: 2.0.0
* Author: Fedya Serafiev
* Author URI: https://fedia.eu
* License: GPL v2 or later
* Text Domain: db-optimizer-pro
*/
if (!defined('ABSPATH')) {
exit;
}
class DB_Optimizer_Pro {
private $plugin_slug = 'db-optimizer-pro';
private $version = '2.0.0';
public function __construct() {
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
add_action('admin_footer', array($this, 'output_admin_scripts'));
add_action('wp_ajax_dbop_scan_database', array($this, 'ajax_scan_database'));
add_action('wp_ajax_dbop_clean_items', array($this, 'ajax_clean_items'));
add_action('wp_ajax_dbop_optimize_tables', array($this, 'ajax_optimize_tables'));
add_action('wp_ajax_dbop_get_stats', array($this, 'ajax_get_stats'));
}
public function add_admin_menu() {
add_menu_page(
'Database Optimizer Pro',
'DB Optimizer',
'manage_options',
$this->plugin_slug,
array($this, 'render_admin_page'),
'dashicons-database-view',
80
);
}
public function enqueue_admin_assets($hook) {
if (strpos($hook, $this->plugin_slug) === false) {
return;
}
// Enqueue jQuery (already included in WordPress)
wp_enqueue_script('jquery');
// Enqueue inline styles
wp_register_style($this->plugin_slug . '-inline', false);
wp_enqueue_style($this->plugin_slug . '-inline');
wp_add_inline_style($this->plugin_slug . '-inline', $this->get_styles());
}
public function output_admin_scripts() {
global $pagenow;
if (isset($_GET['page']) && $_GET['page'] === $this->plugin_slug) {
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
// Define AJAX object
var dbopAjax = {
ajax_url: '<?php echo admin_url('admin-ajax.php'); ?>',
nonce: '<?php echo wp_create_nonce('dbop_nonce'); ?>'
};
var scanData = {};
// Tab switching
$('.dbop-tab').on('click', function(e) {
e.preventDefault();
const tab = $(this).data('tab');
$('.dbop-tab').removeClass('active');
$(this).addClass('active');
$('.dbop-tab-content').removeClass('active');
$('#tab-' + tab).addClass('active');
});
// Scan database
$('#dbop-scan-btn').on('click', function() {
const btn = $(this);
btn.prop('disabled', true).html('<span class="dbop-loading"></span> Сканиране...');
console.log('Sending AJAX request...');
$.ajax({
url: dbopAjax.ajax_url,
type: 'POST',
dataType: 'json',
data: {
action: 'dbop_scan_database',
nonce: dbopAjax.nonce
},
success: function(response) {
console.log('AJAX Response:', response);
if (response.success) {
scanData = response.data;
displayScanResults(response.data);
showAlert('success', 'Сканирането завърши успешно!');
} else {
showAlert('error', response.data?.message || 'Грешка при сканиране');
}
},
error: function(xhr, status, error) {
console.error('AJAX Error:', error);
showAlert('error', 'AJAX грешка: ' + error);
},
complete: function() {
btn.prop('disabled', false).html('🔍 Сканирай база данни');
}
});
});
// Clean items
$(document).on('click', '.dbop-clean-btn', function() {
const type = $(this).data('type');
const btn = $(this);
if (!confirm('Сигурни ли сте, че искате да изтриете тези елементи?')) {
return;
}
btn.prop('disabled', true).html('<span class="dbop-loading"></span>');
$.ajax({
url: dbopAjax.ajax_url,
type: 'POST',
dataType: 'json',
data: {
action: 'dbop_clean_items',
nonce: dbopAjax.nonce,
type: type
},
success: function(response) {
if (response.success) {
showAlert('success', 'Почистването завърши успешно! Изтрити: ' + response.data.deleted);
$('#dbop-scan-btn').click();
} else {
showAlert('error', response.data?.message || 'Грешка при почистване');
}
},
error: function(xhr, status, error) {
showAlert('error', 'AJAX грешка: ' + error);
},
complete: function() {
btn.prop('disabled', false).html('🗑️ Изтрий');
}
});
});
// Optimize tables
$('#dbop-optimize-btn').on('click', function() {
const btn = $(this);
if (!confirm('Искате ли да оптимизирате всички таблици?')) {
return;
}
btn.prop('disabled', true).html('<span class="dbop-loading"></span> Оптимизация...');
$.ajax({
url: dbopAjax.ajax_url,
type: 'POST',
dataType: 'json',
data: {
action: 'dbop_optimize_tables',
nonce: dbopAjax.nonce
},
success: function(response) {
if (response.success) {
showAlert('success', 'Таблиците са оптимизирани успешно!');
updateStats(response.data.stats);
} else {
showAlert('error', response.data?.message || 'Грешка при оптимизация');
}
},
error: function(xhr, status, error) {
showAlert('error', 'AJAX грешка: ' + error);
},
complete: function() {
btn.prop('disabled', false).html('⚡ Оптимизирай таблици');
}
});
});
// Get initial stats
function loadInitialStats() {
$.ajax({
url: dbopAjax.ajax_url,
type: 'POST',
dataType: 'json',
data: {
action: 'dbop_get_stats',
nonce: dbopAjax.nonce
},
success: function(response) {
if (response.success) {
updateStatsUI(response.data);
}
}
});
}
// Call on page load
loadInitialStats();
function displayScanResults(data) {
let html = '';
const items = [
{ key: 'orphaned_postmeta', icon: '🗂️', title: 'Orphaned Post Meta', desc: 'Мета данни без съответен пост' },
{ key: 'orphaned_commentmeta', icon: '💬', title: 'Orphaned Comment Meta', desc: 'Мета данни без съответен коментар' },
{ key: 'orphaned_usermeta', icon: '👤', title: 'Orphaned User Meta', desc: 'Мета данни без съответен потребител' },
{ key: 'orphaned_termmeta', icon: '🏷️', title: 'Orphaned Term Meta', desc: 'Мета данни без съответен термин' },
{ key: 'expired_transients', icon: '⏱️', title: 'Expired Transients', desc: 'Изтекли временни данни' },
{ key: 'post_revisions', icon: '📝', title: 'Post Revisions', desc: 'Ревизии на публикации' },
{ key: 'auto_drafts', icon: '📄', title: 'Auto Drafts', desc: 'Автоматични чернови' },
{ key: 'trashed_posts', icon: '🗑️', title: 'Trashed Posts', desc: 'Изтрити публикации в кошчето' },
{ key: 'spam_comments', icon: '⚠️', title: 'Spam Comments', desc: 'Спам коментари' },
{ key: 'trashed_comments', icon: '💬', title: 'Trashed Comments', desc: 'Изтрити коментари' }
];
items.forEach(item => {
if (data[item.key] > 0) {
html += createResultItem(item.key, item.icon + ' ' + item.title, item.desc, data[item.key]);
}
});
if (html === '') {
html = '<div class="dbop-alert dbop-alert-success">✅ Базата данни е чиста! Няма елементи за почистване.</div>';
}
$('#dbop-results').html(html);
}
function createResultItem(type, title, description, count) {
return `
<div class="dbop-result-item">
<div class="dbop-result-info">
<div class="dbop-result-title">${title}</div>
<div class="dbop-result-description">${description}</div>
</div>
<div style="display: flex; align-items: center;">
<span class="dbop-result-count">${count}</span>
<button class="dbop-btn dbop-btn-danger dbop-clean-btn" data-type="${type}">🗑️ Изтрий</button>
</div>
</div>
`;
}
function updateStatsUI(stats) {
$('#stat-db-size').text(stats.db_size || '0 MB');
$('#stat-tables').text(stats.total_tables || '0');
$('#stat-overhead').text(stats.overhead || '0 MB');
}
function updateStats(stats) {
// Update stats after optimization
if (stats) {
$('#stat-db-size').text(stats.db_size || '0 MB');
$('#stat-tables').text(stats.total_tables || '0');
$('#stat-overhead').text(stats.overhead || '0 MB');
}
}
function showAlert(type, message) {
const alertClass = 'dbop-alert-' + type;
const icon = type === 'success' ? '✅' : (type === 'warning' ? '⚠️' : '❌');
const alert = `<div class="dbop-alert ${alertClass}">${icon} ${message}</div>`;
$('.dbop-main-card').prepend(alert);
setTimeout(function() {
$('.dbop-alert').first().fadeOut(function() {
$(this).remove();
});
}, 5000);
}
// Initialize tabs
$('.dbop-tab[data-tab="cleaner"]').addClass('active');
$('#tab-cleaner').addClass('active');
});
</script>
<?php
}
}
private function get_styles() {
return "
.dbop-container {
max-width: 1400px;
margin: 20px auto;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
}
.dbop-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 40px;
border-radius: 16px;
margin-bottom: 30px;
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.3);
}
.dbop-header h1 {
margin: 0 0 10px 0;
font-size: 32px;
font-weight: 700;
}
.dbop-header p {
margin: 0;
opacity: 0.9;
font-size: 16px;
}
.dbop-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.dbop-card {
background: white;
border-radius: 12px;
padding: 25px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
transition: all 0.3s ease;
border: 1px solid #e5e7eb;
}
.dbop-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.dbop-card h3 {
margin-top: 0;
margin-bottom: 10px;
color: #374151;
font-size: 16px;
font-weight: 600;
}
.dbop-stat-value {
font-size: 32px;
font-weight: 700;
color: #111827;
margin: 10px 0;
}
.dbop-card p {
font-size: 13px;
color: #9ca3af;
margin: 0;
}
.dbop-main-card {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
border: 1px solid #e5e7eb;
margin-bottom: 30px;
}
.dbop-actions {
display: flex;
gap: 15px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.dbop-btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
min-height: 44px;
}
.dbop-btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.dbop-btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.dbop-btn-secondary {
background: #f3f4f6;
color: #374151;
}
.dbop-btn-secondary:hover:not(:disabled) {
background: #e5e7eb;
}
.dbop-btn-danger {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
}
.dbop-btn-danger:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(239, 68, 68, 0.4);
}
.dbop-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none !important;
}
.dbop-results {
margin-top: 30px;
}
.dbop-result-item {
background: #f9fafb;
border-left: 4px solid #667eea;
padding: 15px 20px;
margin-bottom: 12px;
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.2s ease;
}
.dbop-result-item:hover {
background: #f3f4f6;
}
.dbop-result-info {
flex: 1;
}
.dbop-result-title {
font-weight: 600;
color: #111827;
margin-bottom: 4px;
}
.dbop-result-description {
font-size: 13px;
color: #6b7280;
}
.dbop-result-count {
background: #667eea;
color: white;
padding: 6px 12px;
border-radius: 20px;
font-weight: 600;
font-size: 14px;
margin-right: 10px;
}
.dbop-loading {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid rgba(255,255,255,0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.dbop-alert {
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 20px;
display: flex;
align-items: flex-start;
gap: 12px;
}
.dbop-alert-success {
background: #ecfdf5;
color: #065f46;
border: 1px solid #a7f3d0;
}
.dbop-alert-warning {
background: #fef3c7;
color: #92400e;
border: 1px solid #fde68a;
}
.dbop-alert-error {
background: #fee2e2;
color: #991b1b;
border: 1px solid #fecaca;
}
.dbop-alert-info {
background: #eff6ff;
color: #1e40af;
border: 1px solid #bfdbfe;
}
.dbop-tabs {
display: flex;
gap: 10px;
margin-bottom: 25px;
border-bottom: 2px solid #e5e7eb;
flex-wrap: wrap;
}
.dbop-tab {
padding: 12px 20px;
background: none;
border: none;
color: #6b7280;
font-weight: 600;
cursor: pointer;
position: relative;
transition: all 0.3s ease;
border-radius: 6px 6px 0 0;
}
.dbop-tab:hover {
color: #667eea;
background: #f3f4f6;
}
.dbop-tab.active {
color: #667eea;
}
.dbop-tab.active::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
}
.dbop-tab-content {
display: none;
}
.dbop-tab-content.active {
display: block;
}
@media (max-width: 768px) {
.dbop-grid {
grid-template-columns: 1fr;
}
.dbop-actions {
flex-direction: column;
}
.dbop-btn {
width: 100%;
}
.dbop-header {
padding: 25px;
}
.dbop-header h1 {
font-size: 24px;
}
}
";
}
public function render_admin_page() {
global $wpdb;
// Get initial database stats
$stats = $this->get_database_stats();
?>
<div class="dbop-container">
<div class="dbop-header">
<h1>🚀 Database Optimizer Pro</h1>
<p>Професионален инструмент за оптимизация и почистване на WordPress база данни</p>
</div>
<div class="dbop-grid">
<div class="dbop-card">
<h3>Размер на БД</h3>
<div class="dbop-stat-value" id="stat-db-size"><?php echo esc_html($stats['db_size']); ?></div>
<p>Общ размер на базата данни</p>
</div>
<div class="dbop-card">
<h3>Таблици</h3>
<div class="dbop-stat-value" id="stat-tables"><?php echo (int)$stats['total_tables']; ?></div>
<p>Общо таблици в БД</p>
</div>
<div class="dbop-card">
<h3>Overhead</h3>
<div class="dbop-stat-value" id="stat-overhead"><?php echo esc_html($stats['overhead']); ?></div>
<p>Неизползвано място в таблици</p>
</div>
<div class="dbop-card">
<h3>Оптимизации</h3>
<div class="dbop-stat-value"><?php echo (int)get_option('dbop_optimization_count', 0); ?></div>
<p>Брой оптимизации</p>
</div>
</div>
<div class="dbop-main-card">
<nav class="dbop-tabs">
<button class="dbop-tab active" data-tab="cleaner">🧹 Почистване</button>
<button class="dbop-tab" data-tab="optimizer">⚡ Оптимизация</button>
<button class="dbop-tab" data-tab="info">ℹ️ Информация</button>
</nav>
<div id="tab-cleaner" class="dbop-tab-content active">
<div class="dbop-actions">
<button id="dbop-scan-btn" class="dbop-btn dbop-btn-primary">
🔍 Сканирай база данни
</button>
</div>
<div id="dbop-results">
<div class="dbop-alert dbop-alert-warning">
⚠️ Натиснете "Сканирай база данни" за да започнете анализ
</div>
</div>
</div>
<div id="tab-optimizer" class="dbop-tab-content">
<div class="dbop-actions">
<button id="dbop-optimize-btn" class="dbop-btn dbop-btn-primary">
⚡ Оптимизирай таблици
</button>
</div>
<div class="dbop-alert dbop-alert-info">
ℹ️ Оптимизацията ще подобри производителността на базата данни чрез реорганизация на таблиците
</div>
</div>
<div id="tab-info" class="dbop-tab-content">
<h3>За плъгина</h3>
<p>Database Optimizer Pro е професионален инструмент за поддръжка на WordPress база данни.</p>
<h4>Функции:</h4>
<ul>
<li>✅ Почистване на orphaned metadata</li>
<li>✅ Изтриване на изтекли transients</li>
<li>✅ Премахване на ревизии и автосъхранения</li>
<li>✅ Изчистване на спам и изтрити коментари</li>
<li>✅ Оптимизация на всички таблици</li>
<li>✅ Подробна статистика и визуализация</li>
</ul>
<div class="dbop-alert dbop-alert-warning">
⚠️ Препоръчително: Направете backup на базата данни преди да извършвате операции!
</div>
</div>
</div>
<div class="dbop-alert dbop-alert-info">
💡 <strong>Pro Tip:</strong> Редовната поддръжка на базата данни подобрява производителността на сайта.
</div>
</div>
<?php
}
private function get_database_stats() {
global $wpdb;
$db_size = $wpdb->get_var("
SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2)
FROM information_schema.tables
WHERE table_schema = DATABASE()
") . ' MB';
$total_tables = $wpdb->get_var("
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = DATABASE()
");
$overhead = $wpdb->get_var("
SELECT ROUND(SUM(data_free) / 1024 / 1024, 2)
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND ENGINE = 'InnoDB'
") . ' MB';
return array(
'db_size' => $db_size,
'total_tables' => $total_tables,
'overhead' => $overhead
);
}
public function ajax_scan_database() {
check_ajax_referer('dbop_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => 'Недостатъчни права'));
}
global $wpdb;
$data = array(
'orphaned_postmeta' => $this->count_orphaned_postmeta(),
'orphaned_commentmeta' => $this->count_orphaned_commentmeta(),
'orphaned_usermeta' => $this->count_orphaned_usermeta(),
'orphaned_termmeta' => $this->count_orphaned_termmeta(),
'expired_transients' => $this->count_expired_transients(),
'post_revisions' => $this->count_post_revisions(),
'auto_drafts' => $this->count_auto_drafts(),
'trashed_posts' => $this->count_trashed_posts(),
'spam_comments' => $this->count_spam_comments(),
'trashed_comments' => $this->count_trashed_comments()
);
wp_send_json_success($data);
}
public function ajax_clean_items() {
check_ajax_referer('dbop_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => 'Недостатъчни права'));
}
$type = sanitize_text_field($_POST['type']);
$deleted = 0;
switch ($type) {
case 'orphaned_postmeta':
$deleted = $this->clean_orphaned_postmeta();
break;
case 'orphaned_commentmeta':
$deleted = $this->clean_orphaned_commentmeta();
break;
case 'orphaned_usermeta':
$deleted = $this->clean_orphaned_usermeta();
break;
case 'orphaned_termmeta':
$deleted = $this->clean_orphaned_termmeta();
break;
case 'expired_transients':
$deleted = $this->clean_expired_transients();
break;
case 'post_revisions':
$deleted = $this->clean_post_revisions();
break;
case 'auto_drafts':
$deleted = $this->clean_auto_drafts();
break;
case 'trashed_posts':
$deleted = $this->clean_trashed_posts();
break;
case 'spam_comments':
$deleted = $this->clean_spam_comments();
break;
case 'trashed_comments':
$deleted = $this->clean_trashed_comments();
break;
}
wp_send_json_success(array('deleted' => $deleted));
}
public function ajax_optimize_tables() {
check_ajax_referer('dbop_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => 'Недостатъчни права'));
}
global $wpdb;
$optimized = 0;
$tables = $wpdb->get_results("SHOW TABLES", ARRAY_N);
foreach ($tables as $table) {
$table_name = $table[0];
$result = $wpdb->query("OPTIMIZE TABLE `{$table_name}`");
if ($result !== false) {
$optimized++;
}
}
// Update optimization count
$count = get_option('dbop_optimization_count', 0);
update_option('dbop_optimization_count', $count + 1);
wp_send_json_success(array(
'optimized' => $optimized,
'stats' => $this->get_database_stats()
));
}
public function ajax_get_stats() {
check_ajax_referer('dbop_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => 'Недостатъчни права'));
}
wp_send_json_success($this->get_database_stats());
}
// Count methods
private function count_orphaned_postmeta() {
global $wpdb;
return (int)$wpdb->get_var("
SELECT COUNT(*)
FROM {$wpdb->postmeta} pm
LEFT JOIN {$wpdb->posts} p ON pm.post_id = p.ID
WHERE p.ID IS NULL
");
}
private function count_orphaned_commentmeta() {
global $wpdb;
return (int)$wpdb->get_var("
SELECT COUNT(*)
FROM {$wpdb->commentmeta} cm
LEFT JOIN {$wpdb->comments} c ON cm.comment_id = c.comment_ID
WHERE c.comment_ID IS NULL
");
}
private function count_orphaned_usermeta() {
global $wpdb;
return (int)$wpdb->get_var("
SELECT COUNT(*)
FROM {$wpdb->usermeta} um
LEFT JOIN {$wpdb->users} u ON um.user_id = u.ID
WHERE u.ID IS NULL
");
}
private function count_orphaned_termmeta() {
global $wpdb;
return (int)$wpdb->get_var("
SELECT COUNT(*)
FROM {$wpdb->termmeta} tm
LEFT JOIN {$wpdb->terms} t ON tm.term_id = t.term_id
WHERE t.term_id IS NULL
");
}
private function count_expired_transients() {
global $wpdb;
return (int)$wpdb->get_var("
SELECT COUNT(*)
FROM {$wpdb->options}
WHERE option_name LIKE '_transient_timeout_%'
AND option_value < UNIX_TIMESTAMP()
");
}
private function count_post_revisions() {
global $wpdb;
return (int)$wpdb->get_var("
SELECT COUNT(*)
FROM {$wpdb->posts}
WHERE post_type = 'revision'
");
}
private function count_auto_drafts() {
global $wpdb;
return (int)$wpdb->get_var("
SELECT COUNT(*)
FROM {$wpdb->posts}
WHERE post_status = 'auto-draft'
");
}
private function count_trashed_posts() {
global $wpdb;
return (int)$wpdb->get_var("
SELECT COUNT(*)
FROM {$wpdb->posts}
WHERE post_status = 'trash'
");
}
private function count_spam_comments() {
global $wpdb;
return (int)$wpdb->get_var("
SELECT COUNT(*)
FROM {$wpdb->comments}
WHERE comment_approved = 'spam'
");
}
private function count_trashed_comments() {
global $wpdb;
return (int)$wpdb->get_var("
SELECT COUNT(*)
FROM {$wpdb->comments}
WHERE comment_approved = 'trash'
");
}
// Clean methods
private function clean_orphaned_postmeta() {
global $wpdb;
return (int)$wpdb->query("
DELETE pm FROM {$wpdb->postmeta} pm
LEFT JOIN {$wpdb->posts} p ON pm.post_id = p.ID
WHERE p.ID IS NULL
");
}
private function clean_orphaned_commentmeta() {
global $wpdb;
return (int)$wpdb->query("
DELETE cm FROM {$wpdb->commentmeta} cm
LEFT JOIN {$wpdb->comments} c ON cm.comment_id = c.comment_ID
WHERE c.comment_ID IS NULL
");
}
private function clean_orphaned_usermeta() {
global $wpdb;
return (int)$wpdb->query("
DELETE um FROM {$wpdb->usermeta} um
LEFT JOIN {$wpdb->users} u ON um.user_id = u.ID
WHERE u.ID IS NULL
");
}
private function clean_orphaned_termmeta() {
global $wpdb;
return (int)$wpdb->query("
DELETE tm FROM {$wpdb->termmeta} tm
LEFT JOIN {$wpdb->terms} t ON tm.term_id = t.term_id
WHERE t.term_id IS NULL
");
}
private function clean_expired_transients() {
global $wpdb;
$time = time();
$transients = $wpdb->get_col("
SELECT option_name
FROM {$wpdb->options}
WHERE option_name LIKE '_transient_timeout_%'
AND option_value < {$time}
");
$deleted = 0;
foreach ($transients as $transient) {
$key = str_replace('_transient_timeout_', '', $transient);
delete_transient($key);
$deleted++;
}
return $deleted;
}
private function clean_post_revisions() {
global $wpdb;
return (int)$wpdb->query("
DELETE FROM {$wpdb->posts}
WHERE post_type = 'revision'
");
}
private function clean_auto_drafts() {
global $wpdb;
return (int)$wpdb->query("
DELETE FROM {$wpdb->posts}
WHERE post_status = 'auto-draft'
");
}
private function clean_trashed_posts() {
global $wpdb;
return (int)$wpdb->query("
DELETE FROM {$wpdb->posts}
WHERE post_status = 'trash'
");
}
private function clean_spam_comments() {
global $wpdb;
return (int)$wpdb->query("
DELETE FROM {$wpdb->comments}
WHERE comment_approved = 'spam'
");
}
private function clean_trashed_comments() {
global $wpdb;
return (int)$wpdb->query("
DELETE FROM {$wpdb->comments}
WHERE comment_approved = 'trash'
");
}
}
// Initialize the plugin
function db_optimizer_pro_init() {
new DB_Optimizer_Pro();
}
add_action('plugins_loaded', 'db_optimizer_pro_init');
| 1 | <?php |
| 2 | /** |
| 3 | * Plugin Name: Database Optimizer Pro |
| 4 | * Plugin URI: https://fedia.eu |
| 5 | * Description: Професионален инструмент за оптимизация на WordPress база данни с модерен интерфейс |
| 6 | * Version: 2.0.0 |
| 7 | * Author: Fedya Serafiev |
| 8 | * Author URI: https://fedia.eu |
| 9 | * License: GPL v2 or later |
| 10 | * Text Domain: db-optimizer-pro |
| 11 | */ |
| 12 | |
| 13 | if (!defined('ABSPATH')) { |
| 14 | exit; |
| 15 | } |
| 16 | |
| 17 | class DB_Optimizer_Pro { |
| 18 | |
| 19 | private $plugin_slug = 'db-optimizer-pro'; |
| 20 | private $version = '2.0.0'; |
| 21 | |
| 22 | public function __construct() { |
| 23 | add_action('admin_menu', array($this, 'add_admin_menu')); |
| 24 | add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); |
| 25 | add_action('admin_footer', array($this, 'output_admin_scripts')); |
| 26 | add_action('wp_ajax_dbop_scan_database', array($this, 'ajax_scan_database')); |
| 27 | add_action('wp_ajax_dbop_clean_items', array($this, 'ajax_clean_items')); |
| 28 | add_action('wp_ajax_dbop_optimize_tables', array($this, 'ajax_optimize_tables')); |
| 29 | add_action('wp_ajax_dbop_get_stats', array($this, 'ajax_get_stats')); |
| 30 | } |
| 31 | |
| 32 | public function add_admin_menu() { |
| 33 | add_menu_page( |
| 34 | 'Database Optimizer Pro', |
| 35 | 'DB Optimizer', |
| 36 | 'manage_options', |
| 37 | $this->plugin_slug, |
| 38 | array($this, 'render_admin_page'), |
| 39 | 'dashicons-database-view', |
| 40 | 80 |
| 41 | ); |
| 42 | } |
| 43 | |
| 44 | public function enqueue_admin_assets($hook) { |
| 45 | if (strpos($hook, $this->plugin_slug) === false) { |
| 46 | return; |
| 47 | } |
| 48 | |
| 49 | // Enqueue jQuery (already included in WordPress) |
| 50 | wp_enqueue_script('jquery'); |
| 51 | |
| 52 | // Enqueue inline styles |
| 53 | wp_register_style($this->plugin_slug . '-inline', false); |
| 54 | wp_enqueue_style($this->plugin_slug . '-inline'); |
| 55 | wp_add_inline_style($this->plugin_slug . '-inline', $this->get_styles()); |
| 56 | } |
| 57 | |
| 58 | public function output_admin_scripts() { |
| 59 | global $pagenow; |
| 60 | |
| 61 | if (isset($_GET['page']) && $_GET['page'] === $this->plugin_slug) { |
| 62 | ?> |
| 63 | <script type="text/javascript"> |
| 64 | jQuery(document).ready(function($) { |
| 65 | // Define AJAX object |
| 66 | var dbopAjax = { |
| 67 | ajax_url: '<?php echo admin_url('admin-ajax.php'); ?>', |
| 68 | nonce: '<?php echo wp_create_nonce('dbop_nonce'); ?>' |
| 69 | }; |
| 70 | |
| 71 | var scanData = {}; |
| 72 | |
| 73 | // Tab switching |
| 74 | $('.dbop-tab').on('click', function(e) { |
| 75 | e.preventDefault(); |
| 76 | const tab = $(this).data('tab'); |
| 77 | $('.dbop-tab').removeClass('active'); |
| 78 | $(this).addClass('active'); |
| 79 | $('.dbop-tab-content').removeClass('active'); |
| 80 | $('#tab-' + tab).addClass('active'); |
| 81 | }); |
| 82 | |
| 83 | // Scan database |
| 84 | $('#dbop-scan-btn').on('click', function() { |
| 85 | const btn = $(this); |
| 86 | btn.prop('disabled', true).html('<span class="dbop-loading"></span> Сканиране...'); |
| 87 | |
| 88 | console.log('Sending AJAX request...'); |
| 89 | |
| 90 | $.ajax({ |
| 91 | url: dbopAjax.ajax_url, |
| 92 | type: 'POST', |
| 93 | dataType: 'json', |
| 94 | data: { |
| 95 | action: 'dbop_scan_database', |
| 96 | nonce: dbopAjax.nonce |
| 97 | }, |
| 98 | success: function(response) { |
| 99 | console.log('AJAX Response:', response); |
| 100 | if (response.success) { |
| 101 | scanData = response.data; |
| 102 | displayScanResults(response.data); |
| 103 | showAlert('success', 'Сканирането завърши успешно!'); |
| 104 | } else { |
| 105 | showAlert('error', response.data?.message || 'Грешка при сканиране'); |
| 106 | } |
| 107 | }, |
| 108 | error: function(xhr, status, error) { |
| 109 | console.error('AJAX Error:', error); |
| 110 | showAlert('error', 'AJAX грешка: ' + error); |
| 111 | }, |
| 112 | complete: function() { |
| 113 | btn.prop('disabled', false).html('🔍 Сканирай база данни'); |
| 114 | } |
| 115 | }); |
| 116 | }); |
| 117 | |
| 118 | // Clean items |
| 119 | $(document).on('click', '.dbop-clean-btn', function() { |
| 120 | const type = $(this).data('type'); |
| 121 | const btn = $(this); |
| 122 | |
| 123 | if (!confirm('Сигурни ли сте, че искате да изтриете тези елементи?')) { |
| 124 | return; |
| 125 | } |
| 126 | |
| 127 | btn.prop('disabled', true).html('<span class="dbop-loading"></span>'); |
| 128 | |
| 129 | $.ajax({ |
| 130 | url: dbopAjax.ajax_url, |
| 131 | type: 'POST', |
| 132 | dataType: 'json', |
| 133 | data: { |
| 134 | action: 'dbop_clean_items', |
| 135 | nonce: dbopAjax.nonce, |
| 136 | type: type |
| 137 | }, |
| 138 | success: function(response) { |
| 139 | if (response.success) { |
| 140 | showAlert('success', 'Почистването завърши успешно! Изтрити: ' + response.data.deleted); |
| 141 | $('#dbop-scan-btn').click(); |
| 142 | } else { |
| 143 | showAlert('error', response.data?.message || 'Грешка при почистване'); |
| 144 | } |
| 145 | }, |
| 146 | error: function(xhr, status, error) { |
| 147 | showAlert('error', 'AJAX грешка: ' + error); |
| 148 | }, |
| 149 | complete: function() { |
| 150 | btn.prop('disabled', false).html('🗑️ Изтрий'); |
| 151 | } |
| 152 | }); |
| 153 | }); |
| 154 | |
| 155 | // Optimize tables |
| 156 | $('#dbop-optimize-btn').on('click', function() { |
| 157 | const btn = $(this); |
| 158 | |
| 159 | if (!confirm('Искате ли да оптимизирате всички таблици?')) { |
| 160 | return; |
| 161 | } |
| 162 | |
| 163 | btn.prop('disabled', true).html('<span class="dbop-loading"></span> Оптимизация...'); |
| 164 | |
| 165 | $.ajax({ |
| 166 | url: dbopAjax.ajax_url, |
| 167 | type: 'POST', |
| 168 | dataType: 'json', |
| 169 | data: { |
| 170 | action: 'dbop_optimize_tables', |
| 171 | nonce: dbopAjax.nonce |
| 172 | }, |
| 173 | success: function(response) { |
| 174 | if (response.success) { |
| 175 | showAlert('success', 'Таблиците са оптимизирани успешно!'); |
| 176 | updateStats(response.data.stats); |
| 177 | } else { |
| 178 | showAlert('error', response.data?.message || 'Грешка при оптимизация'); |
| 179 | } |
| 180 | }, |
| 181 | error: function(xhr, status, error) { |
| 182 | showAlert('error', 'AJAX грешка: ' + error); |
| 183 | }, |
| 184 | complete: function() { |
| 185 | btn.prop('disabled', false).html('⚡ Оптимизирай таблици'); |
| 186 | } |
| 187 | }); |
| 188 | }); |
| 189 | |
| 190 | // Get initial stats |
| 191 | function loadInitialStats() { |
| 192 | $.ajax({ |
| 193 | url: dbopAjax.ajax_url, |
| 194 | type: 'POST', |
| 195 | dataType: 'json', |
| 196 | data: { |
| 197 | action: 'dbop_get_stats', |
| 198 | nonce: dbopAjax.nonce |
| 199 | }, |
| 200 | success: function(response) { |
| 201 | if (response.success) { |
| 202 | updateStatsUI(response.data); |
| 203 | } |
| 204 | } |
| 205 | }); |
| 206 | } |
| 207 | |
| 208 | // Call on page load |
| 209 | loadInitialStats(); |
| 210 | |
| 211 | function displayScanResults(data) { |
| 212 | let html = ''; |
| 213 | |
| 214 | const items = [ |
| 215 | { key: 'orphaned_postmeta', icon: '🗂️', title: 'Orphaned Post Meta', desc: 'Мета данни без съответен пост' }, |
| 216 | { key: 'orphaned_commentmeta', icon: '💬', title: 'Orphaned Comment Meta', desc: 'Мета данни без съответен коментар' }, |
| 217 | { key: 'orphaned_usermeta', icon: '👤', title: 'Orphaned User Meta', desc: 'Мета данни без съответен потребител' }, |
| 218 | { key: 'orphaned_termmeta', icon: '🏷️', title: 'Orphaned Term Meta', desc: 'Мета данни без съответен термин' }, |
| 219 | { key: 'expired_transients', icon: '⏱️', title: 'Expired Transients', desc: 'Изтекли временни данни' }, |
| 220 | { key: 'post_revisions', icon: '📝', title: 'Post Revisions', desc: 'Ревизии на публикации' }, |
| 221 | { key: 'auto_drafts', icon: '📄', title: 'Auto Drafts', desc: 'Автоматични чернови' }, |
| 222 | { key: 'trashed_posts', icon: '🗑️', title: 'Trashed Posts', desc: 'Изтрити публикации в кошчето' }, |
| 223 | { key: 'spam_comments', icon: '⚠️', title: 'Spam Comments', desc: 'Спам коментари' }, |
| 224 | { key: 'trashed_comments', icon: '💬', title: 'Trashed Comments', desc: 'Изтрити коментари' } |
| 225 | ]; |
| 226 | |
| 227 | items.forEach(item => { |
| 228 | if (data[item.key] > 0) { |
| 229 | html += createResultItem(item.key, item.icon + ' ' + item.title, item.desc, data[item.key]); |
| 230 | } |
| 231 | }); |
| 232 | |
| 233 | if (html === '') { |
| 234 | html = '<div class="dbop-alert dbop-alert-success">✅ Базата данни е чиста! Няма елементи за почистване.</div>'; |
| 235 | } |
| 236 | |
| 237 | $('#dbop-results').html(html); |
| 238 | } |
| 239 | |
| 240 | function createResultItem(type, title, description, count) { |
| 241 | return ` |
| 242 | <div class="dbop-result-item"> |
| 243 | <div class="dbop-result-info"> |
| 244 | <div class="dbop-result-title">${title}</div> |
| 245 | <div class="dbop-result-description">${description}</div> |
| 246 | </div> |
| 247 | <div style="display: flex; align-items: center;"> |
| 248 | <span class="dbop-result-count">${count}</span> |
| 249 | <button class="dbop-btn dbop-btn-danger dbop-clean-btn" data-type="${type}">🗑️ Изтрий</button> |
| 250 | </div> |
| 251 | </div> |
| 252 | `; |
| 253 | } |
| 254 | |
| 255 | function updateStatsUI(stats) { |
| 256 | $('#stat-db-size').text(stats.db_size || '0 MB'); |
| 257 | $('#stat-tables').text(stats.total_tables || '0'); |
| 258 | $('#stat-overhead').text(stats.overhead || '0 MB'); |
| 259 | } |
| 260 | |
| 261 | function updateStats(stats) { |
| 262 | // Update stats after optimization |
| 263 | if (stats) { |
| 264 | $('#stat-db-size').text(stats.db_size || '0 MB'); |
| 265 | $('#stat-tables').text(stats.total_tables || '0'); |
| 266 | $('#stat-overhead').text(stats.overhead || '0 MB'); |
| 267 | } |
| 268 | } |
| 269 | |
| 270 | function showAlert(type, message) { |
| 271 | const alertClass = 'dbop-alert-' + type; |
| 272 | const icon = type === 'success' ? '✅' : (type === 'warning' ? '⚠️' : '❌'); |
| 273 | const alert = `<div class="dbop-alert ${alertClass}">${icon} ${message}</div>`; |
| 274 | |
| 275 | $('.dbop-main-card').prepend(alert); |
| 276 | |
| 277 | setTimeout(function() { |
| 278 | $('.dbop-alert').first().fadeOut(function() { |
| 279 | $(this).remove(); |
| 280 | }); |
| 281 | }, 5000); |
| 282 | } |
| 283 | |
| 284 | // Initialize tabs |
| 285 | $('.dbop-tab[data-tab="cleaner"]').addClass('active'); |
| 286 | $('#tab-cleaner').addClass('active'); |
| 287 | }); |
| 288 | </script> |
| 289 | <?php |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | private function get_styles() { |
| 294 | return " |
| 295 | .dbop-container { |
| 296 | max-width: 1400px; |
| 297 | margin: 20px auto; |
| 298 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; |
| 299 | } |
| 300 | |
| 301 | .dbop-header { |
| 302 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| 303 | color: white; |
| 304 | padding: 40px; |
| 305 | border-radius: 16px; |
| 306 | margin-bottom: 30px; |
| 307 | box-shadow: 0 10px 40px rgba(102, 126, 234, 0.3); |
| 308 | } |
| 309 | |
| 310 | .dbop-header h1 { |
| 311 | margin: 0 0 10px 0; |
| 312 | font-size: 32px; |
| 313 | font-weight: 700; |
| 314 | } |
| 315 | |
| 316 | .dbop-header p { |
| 317 | margin: 0; |
| 318 | opacity: 0.9; |
| 319 | font-size: 16px; |
| 320 | } |
| 321 | |
| 322 | .dbop-grid { |
| 323 | display: grid; |
| 324 | grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); |
| 325 | gap: 20px; |
| 326 | margin-bottom: 30px; |
| 327 | } |
| 328 | |
| 329 | .dbop-card { |
| 330 | background: white; |
| 331 | border-radius: 12px; |
| 332 | padding: 25px; |
| 333 | box-shadow: 0 2px 12px rgba(0,0,0,0.08); |
| 334 | transition: all 0.3s ease; |
| 335 | border: 1px solid #e5e7eb; |
| 336 | } |
| 337 | |
| 338 | .dbop-card:hover { |
| 339 | transform: translateY(-4px); |
| 340 | box-shadow: 0 8px 24px rgba(0,0,0,0.12); |
| 341 | } |
| 342 | |
| 343 | .dbop-card h3 { |
| 344 | margin-top: 0; |
| 345 | margin-bottom: 10px; |
| 346 | color: #374151; |
| 347 | font-size: 16px; |
| 348 | font-weight: 600; |
| 349 | } |
| 350 | |
| 351 | .dbop-stat-value { |
| 352 | font-size: 32px; |
| 353 | font-weight: 700; |
| 354 | color: #111827; |
| 355 | margin: 10px 0; |
| 356 | } |
| 357 | |
| 358 | .dbop-card p { |
| 359 | font-size: 13px; |
| 360 | color: #9ca3af; |
| 361 | margin: 0; |
| 362 | } |
| 363 | |
| 364 | .dbop-main-card { |
| 365 | background: white; |
| 366 | border-radius: 12px; |
| 367 | padding: 30px; |
| 368 | box-shadow: 0 2px 12px rgba(0,0,0,0.08); |
| 369 | border: 1px solid #e5e7eb; |
| 370 | margin-bottom: 30px; |
| 371 | } |
| 372 | |
| 373 | .dbop-actions { |
| 374 | display: flex; |
| 375 | gap: 15px; |
| 376 | margin-bottom: 30px; |
| 377 | flex-wrap: wrap; |
| 378 | } |
| 379 | |
| 380 | .dbop-btn { |
| 381 | padding: 12px 24px; |
| 382 | border: none; |
| 383 | border-radius: 8px; |
| 384 | font-size: 14px; |
| 385 | font-weight: 600; |
| 386 | cursor: pointer; |
| 387 | transition: all 0.3s ease; |
| 388 | display: inline-flex; |
| 389 | align-items: center; |
| 390 | justify-content: center; |
| 391 | gap: 8px; |
| 392 | min-height: 44px; |
| 393 | } |
| 394 | |
| 395 | .dbop-btn-primary { |
| 396 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| 397 | color: white; |
| 398 | } |
| 399 | |
| 400 | .dbop-btn-primary:hover:not(:disabled) { |
| 401 | transform: translateY(-2px); |
| 402 | box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); |
| 403 | } |
| 404 | |
| 405 | .dbop-btn-secondary { |
| 406 | background: #f3f4f6; |
| 407 | color: #374151; |
| 408 | } |
| 409 | |
| 410 | .dbop-btn-secondary:hover:not(:disabled) { |
| 411 | background: #e5e7eb; |
| 412 | } |
| 413 | |
| 414 | .dbop-btn-danger { |
| 415 | background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); |
| 416 | color: white; |
| 417 | } |
| 418 | |
| 419 | .dbop-btn-danger:hover:not(:disabled) { |
| 420 | transform: translateY(-2px); |
| 421 | box-shadow: 0 6px 20px rgba(239, 68, 68, 0.4); |
| 422 | } |
| 423 | |
| 424 | .dbop-btn:disabled { |
| 425 | opacity: 0.5; |
| 426 | cursor: not-allowed; |
| 427 | transform: none !important; |
| 428 | } |
| 429 | |
| 430 | .dbop-results { |
| 431 | margin-top: 30px; |
| 432 | } |
| 433 | |
| 434 | .dbop-result-item { |
| 435 | background: #f9fafb; |
| 436 | border-left: 4px solid #667eea; |
| 437 | padding: 15px 20px; |
| 438 | margin-bottom: 12px; |
| 439 | border-radius: 8px; |
| 440 | display: flex; |
| 441 | justify-content: space-between; |
| 442 | align-items: center; |
| 443 | transition: all 0.2s ease; |
| 444 | } |
| 445 | |
| 446 | .dbop-result-item:hover { |
| 447 | background: #f3f4f6; |
| 448 | } |
| 449 | |
| 450 | .dbop-result-info { |
| 451 | flex: 1; |
| 452 | } |
| 453 | |
| 454 | .dbop-result-title { |
| 455 | font-weight: 600; |
| 456 | color: #111827; |
| 457 | margin-bottom: 4px; |
| 458 | } |
| 459 | |
| 460 | .dbop-result-description { |
| 461 | font-size: 13px; |
| 462 | color: #6b7280; |
| 463 | } |
| 464 | |
| 465 | .dbop-result-count { |
| 466 | background: #667eea; |
| 467 | color: white; |
| 468 | padding: 6px 12px; |
| 469 | border-radius: 20px; |
| 470 | font-weight: 600; |
| 471 | font-size: 14px; |
| 472 | margin-right: 10px; |
| 473 | } |
| 474 | |
| 475 | .dbop-loading { |
| 476 | display: inline-block; |
| 477 | width: 16px; |
| 478 | height: 16px; |
| 479 | border: 2px solid rgba(255,255,255,0.3); |
| 480 | border-top-color: white; |
| 481 | border-radius: 50%; |
| 482 | animation: spin 0.8s linear infinite; |
| 483 | } |
| 484 | |
| 485 | @keyframes spin { |
| 486 | to { transform: rotate(360deg); } |
| 487 | } |
| 488 | |
| 489 | .dbop-alert { |
| 490 | padding: 15px 20px; |
| 491 | border-radius: 8px; |
| 492 | margin-bottom: 20px; |
| 493 | display: flex; |
| 494 | align-items: flex-start; |
| 495 | gap: 12px; |
| 496 | } |
| 497 | |
| 498 | .dbop-alert-success { |
| 499 | background: #ecfdf5; |
| 500 | color: #065f46; |
| 501 | border: 1px solid #a7f3d0; |
| 502 | } |
| 503 | |
| 504 | .dbop-alert-warning { |
| 505 | background: #fef3c7; |
| 506 | color: #92400e; |
| 507 | border: 1px solid #fde68a; |
| 508 | } |
| 509 | |
| 510 | .dbop-alert-error { |
| 511 | background: #fee2e2; |
| 512 | color: #991b1b; |
| 513 | border: 1px solid #fecaca; |
| 514 | } |
| 515 | |
| 516 | .dbop-alert-info { |
| 517 | background: #eff6ff; |
| 518 | color: #1e40af; |
| 519 | border: 1px solid #bfdbfe; |
| 520 | } |
| 521 | |
| 522 | .dbop-tabs { |
| 523 | display: flex; |
| 524 | gap: 10px; |
| 525 | margin-bottom: 25px; |
| 526 | border-bottom: 2px solid #e5e7eb; |
| 527 | flex-wrap: wrap; |
| 528 | } |
| 529 | |
| 530 | .dbop-tab { |
| 531 | padding: 12px 20px; |
| 532 | background: none; |
| 533 | border: none; |
| 534 | color: #6b7280; |
| 535 | font-weight: 600; |
| 536 | cursor: pointer; |
| 537 | position: relative; |
| 538 | transition: all 0.3s ease; |
| 539 | border-radius: 6px 6px 0 0; |
| 540 | } |
| 541 | |
| 542 | .dbop-tab:hover { |
| 543 | color: #667eea; |
| 544 | background: #f3f4f6; |
| 545 | } |
| 546 | |
| 547 | .dbop-tab.active { |
| 548 | color: #667eea; |
| 549 | } |
| 550 | |
| 551 | .dbop-tab.active::after { |
| 552 | content: ''; |
| 553 | position: absolute; |
| 554 | bottom: -2px; |
| 555 | left: 0; |
| 556 | right: 0; |
| 557 | height: 2px; |
| 558 | background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); |
| 559 | } |
| 560 | |
| 561 | .dbop-tab-content { |
| 562 | display: none; |
| 563 | } |
| 564 | |
| 565 | .dbop-tab-content.active { |
| 566 | display: block; |
| 567 | } |
| 568 | |
| 569 | @media (max-width: 768px) { |
| 570 | .dbop-grid { |
| 571 | grid-template-columns: 1fr; |
| 572 | } |
| 573 | |
| 574 | .dbop-actions { |
| 575 | flex-direction: column; |
| 576 | } |
| 577 | |
| 578 | .dbop-btn { |
| 579 | width: 100%; |
| 580 | } |
| 581 | |
| 582 | .dbop-header { |
| 583 | padding: 25px; |
| 584 | } |
| 585 | |
| 586 | .dbop-header h1 { |
| 587 | font-size: 24px; |
| 588 | } |
| 589 | } |
| 590 | "; |
| 591 | } |
| 592 | |
| 593 | public function render_admin_page() { |
| 594 | global $wpdb; |
| 595 | |
| 596 | // Get initial database stats |
| 597 | $stats = $this->get_database_stats(); |
| 598 | ?> |
| 599 | <div class="dbop-container"> |
| 600 | <div class="dbop-header"> |
| 601 | <h1>🚀 Database Optimizer Pro</h1> |
| 602 | <p>Професионален инструмент за оптимизация и почистване на WordPress база данни</p> |
| 603 | </div> |
| 604 | |
| 605 | <div class="dbop-grid"> |
| 606 | <div class="dbop-card"> |
| 607 | <h3>Размер на БД</h3> |
| 608 | <div class="dbop-stat-value" id="stat-db-size"><?php echo esc_html($stats['db_size']); ?></div> |
| 609 | <p>Общ размер на базата данни</p> |
| 610 | </div> |
| 611 | |
| 612 | <div class="dbop-card"> |
| 613 | <h3>Таблици</h3> |
| 614 | <div class="dbop-stat-value" id="stat-tables"><?php echo (int)$stats['total_tables']; ?></div> |
| 615 | <p>Общо таблици в БД</p> |
| 616 | </div> |
| 617 | |
| 618 | <div class="dbop-card"> |
| 619 | <h3>Overhead</h3> |
| 620 | <div class="dbop-stat-value" id="stat-overhead"><?php echo esc_html($stats['overhead']); ?></div> |
| 621 | <p>Неизползвано място в таблици</p> |
| 622 | </div> |
| 623 | |
| 624 | <div class="dbop-card"> |
| 625 | <h3>Оптимизации</h3> |
| 626 | <div class="dbop-stat-value"><?php echo (int)get_option('dbop_optimization_count', 0); ?></div> |
| 627 | <p>Брой оптимизации</p> |
| 628 | </div> |
| 629 | </div> |
| 630 | |
| 631 | <div class="dbop-main-card"> |
| 632 | <nav class="dbop-tabs"> |
| 633 | <button class="dbop-tab active" data-tab="cleaner">🧹 Почистване</button> |
| 634 | <button class="dbop-tab" data-tab="optimizer">⚡ Оптимизация</button> |
| 635 | <button class="dbop-tab" data-tab="info">ℹ️ Информация</button> |
| 636 | </nav> |
| 637 | |
| 638 | <div id="tab-cleaner" class="dbop-tab-content active"> |
| 639 | <div class="dbop-actions"> |
| 640 | <button id="dbop-scan-btn" class="dbop-btn dbop-btn-primary"> |
| 641 | 🔍 Сканирай база данни |
| 642 | </button> |
| 643 | </div> |
| 644 | |
| 645 | <div id="dbop-results"> |
| 646 | <div class="dbop-alert dbop-alert-warning"> |
| 647 | ⚠️ Натиснете "Сканирай база данни" за да започнете анализ |
| 648 | </div> |
| 649 | </div> |
| 650 | </div> |
| 651 | |
| 652 | <div id="tab-optimizer" class="dbop-tab-content"> |
| 653 | <div class="dbop-actions"> |
| 654 | <button id="dbop-optimize-btn" class="dbop-btn dbop-btn-primary"> |
| 655 | ⚡ Оптимизирай таблици |
| 656 | </button> |
| 657 | </div> |
| 658 | |
| 659 | <div class="dbop-alert dbop-alert-info"> |
| 660 | ℹ️ Оптимизацията ще подобри производителността на базата данни чрез реорганизация на таблиците |
| 661 | </div> |
| 662 | </div> |
| 663 | |
| 664 | <div id="tab-info" class="dbop-tab-content"> |
| 665 | <h3>За плъгина</h3> |
| 666 | <p>Database Optimizer Pro е професионален инструмент за поддръжка на WordPress база данни.</p> |
| 667 | |
| 668 | <h4>Функции:</h4> |
| 669 | <ul> |
| 670 | <li>✅ Почистване на orphaned metadata</li> |
| 671 | <li>✅ Изтриване на изтекли transients</li> |
| 672 | <li>✅ Премахване на ревизии и автосъхранения</li> |
| 673 | <li>✅ Изчистване на спам и изтрити коментари</li> |
| 674 | <li>✅ Оптимизация на всички таблици</li> |
| 675 | <li>✅ Подробна статистика и визуализация</li> |
| 676 | </ul> |
| 677 | |
| 678 | <div class="dbop-alert dbop-alert-warning"> |
| 679 | ⚠️ Препоръчително: Направете backup на базата данни преди да извършвате операции! |
| 680 | </div> |
| 681 | </div> |
| 682 | </div> |
| 683 | |
| 684 | <div class="dbop-alert dbop-alert-info"> |
| 685 | 💡 <strong>Pro Tip:</strong> Редовната поддръжка на базата данни подобрява производителността на сайта. |
| 686 | </div> |
| 687 | </div> |
| 688 | <?php |
| 689 | } |
| 690 | |
| 691 | private function get_database_stats() { |
| 692 | global $wpdb; |
| 693 | |
| 694 | $db_size = $wpdb->get_var(" |
| 695 | SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) |
| 696 | FROM information_schema.tables |
| 697 | WHERE table_schema = DATABASE() |
| 698 | ") . ' MB'; |
| 699 | |
| 700 | $total_tables = $wpdb->get_var(" |
| 701 | SELECT COUNT(*) |
| 702 | FROM information_schema.tables |
| 703 | WHERE table_schema = DATABASE() |
| 704 | "); |
| 705 | |
| 706 | $overhead = $wpdb->get_var(" |
| 707 | SELECT ROUND(SUM(data_free) / 1024 / 1024, 2) |
| 708 | FROM information_schema.tables |
| 709 | WHERE table_schema = DATABASE() |
| 710 | AND ENGINE = 'InnoDB' |
| 711 | ") . ' MB'; |
| 712 | |
| 713 | return array( |
| 714 | 'db_size' => $db_size, |
| 715 | 'total_tables' => $total_tables, |
| 716 | 'overhead' => $overhead |
| 717 | ); |
| 718 | } |
| 719 | |
| 720 | public function ajax_scan_database() { |
| 721 | check_ajax_referer('dbop_nonce', 'nonce'); |
| 722 | |
| 723 | if (!current_user_can('manage_options')) { |
| 724 | wp_send_json_error(array('message' => 'Недостатъчни права')); |
| 725 | } |
| 726 | |
| 727 | global $wpdb; |
| 728 | |
| 729 | $data = array( |
| 730 | 'orphaned_postmeta' => $this->count_orphaned_postmeta(), |
| 731 | 'orphaned_commentmeta' => $this->count_orphaned_commentmeta(), |
| 732 | 'orphaned_usermeta' => $this->count_orphaned_usermeta(), |
| 733 | 'orphaned_termmeta' => $this->count_orphaned_termmeta(), |
| 734 | 'expired_transients' => $this->count_expired_transients(), |
| 735 | 'post_revisions' => $this->count_post_revisions(), |
| 736 | 'auto_drafts' => $this->count_auto_drafts(), |
| 737 | 'trashed_posts' => $this->count_trashed_posts(), |
| 738 | 'spam_comments' => $this->count_spam_comments(), |
| 739 | 'trashed_comments' => $this->count_trashed_comments() |
| 740 | ); |
| 741 | |
| 742 | wp_send_json_success($data); |
| 743 | } |
| 744 | |
| 745 | public function ajax_clean_items() { |
| 746 | check_ajax_referer('dbop_nonce', 'nonce'); |
| 747 | |
| 748 | if (!current_user_can('manage_options')) { |
| 749 | wp_send_json_error(array('message' => 'Недостатъчни права')); |
| 750 | } |
| 751 | |
| 752 | $type = sanitize_text_field($_POST['type']); |
| 753 | $deleted = 0; |
| 754 | |
| 755 | switch ($type) { |
| 756 | case 'orphaned_postmeta': |
| 757 | $deleted = $this->clean_orphaned_postmeta(); |
| 758 | break; |
| 759 | case 'orphaned_commentmeta': |
| 760 | $deleted = $this->clean_orphaned_commentmeta(); |
| 761 | break; |
| 762 | case 'orphaned_usermeta': |
| 763 | $deleted = $this->clean_orphaned_usermeta(); |
| 764 | break; |
| 765 | case 'orphaned_termmeta': |
| 766 | $deleted = $this->clean_orphaned_termmeta(); |
| 767 | break; |
| 768 | case 'expired_transients': |
| 769 | $deleted = $this->clean_expired_transients(); |
| 770 | break; |
| 771 | case 'post_revisions': |
| 772 | $deleted = $this->clean_post_revisions(); |
| 773 | break; |
| 774 | case 'auto_drafts': |
| 775 | $deleted = $this->clean_auto_drafts(); |
| 776 | break; |
| 777 | case 'trashed_posts': |
| 778 | $deleted = $this->clean_trashed_posts(); |
| 779 | break; |
| 780 | case 'spam_comments': |
| 781 | $deleted = $this->clean_spam_comments(); |
| 782 | break; |
| 783 | case 'trashed_comments': |
| 784 | $deleted = $this->clean_trashed_comments(); |
| 785 | break; |
| 786 | } |
| 787 | |
| 788 | wp_send_json_success(array('deleted' => $deleted)); |
| 789 | } |
| 790 | |
| 791 | public function ajax_optimize_tables() { |
| 792 | check_ajax_referer('dbop_nonce', 'nonce'); |
| 793 | |
| 794 | if (!current_user_can('manage_options')) { |
| 795 | wp_send_json_error(array('message' => 'Недостатъчни права')); |
| 796 | } |
| 797 | |
| 798 | global $wpdb; |
| 799 | $optimized = 0; |
| 800 | |
| 801 | $tables = $wpdb->get_results("SHOW TABLES", ARRAY_N); |
| 802 | |
| 803 | foreach ($tables as $table) { |
| 804 | $table_name = $table[0]; |
| 805 | $result = $wpdb->query("OPTIMIZE TABLE `{$table_name}`"); |
| 806 | if ($result !== false) { |
| 807 | $optimized++; |
| 808 | } |
| 809 | } |
| 810 | |
| 811 | // Update optimization count |
| 812 | $count = get_option('dbop_optimization_count', 0); |
| 813 | update_option('dbop_optimization_count', $count + 1); |
| 814 | |
| 815 | wp_send_json_success(array( |
| 816 | 'optimized' => $optimized, |
| 817 | 'stats' => $this->get_database_stats() |
| 818 | )); |
| 819 | } |
| 820 | |
| 821 | public function ajax_get_stats() { |
| 822 | check_ajax_referer('dbop_nonce', 'nonce'); |
| 823 | |
| 824 | if (!current_user_can('manage_options')) { |
| 825 | wp_send_json_error(array('message' => 'Недостатъчни права')); |
| 826 | } |
| 827 | |
| 828 | wp_send_json_success($this->get_database_stats()); |
| 829 | } |
| 830 | |
| 831 | // Count methods |
| 832 | private function count_orphaned_postmeta() { |
| 833 | global $wpdb; |
| 834 | return (int)$wpdb->get_var(" |
| 835 | SELECT COUNT(*) |
| 836 | FROM {$wpdb->postmeta} pm |
| 837 | LEFT JOIN {$wpdb->posts} p ON pm.post_id = p.ID |
| 838 | WHERE p.ID IS NULL |
| 839 | "); |
| 840 | } |
| 841 | |
| 842 | private function count_orphaned_commentmeta() { |
| 843 | global $wpdb; |
| 844 | return (int)$wpdb->get_var(" |
| 845 | SELECT COUNT(*) |
| 846 | FROM {$wpdb->commentmeta} cm |
| 847 | LEFT JOIN {$wpdb->comments} c ON cm.comment_id = c.comment_ID |
| 848 | WHERE c.comment_ID IS NULL |
| 849 | "); |
| 850 | } |
| 851 | |
| 852 | private function count_orphaned_usermeta() { |
| 853 | global $wpdb; |
| 854 | return (int)$wpdb->get_var(" |
| 855 | SELECT COUNT(*) |
| 856 | FROM {$wpdb->usermeta} um |
| 857 | LEFT JOIN {$wpdb->users} u ON um.user_id = u.ID |
| 858 | WHERE u.ID IS NULL |
| 859 | "); |
| 860 | } |
| 861 | |
| 862 | private function count_orphaned_termmeta() { |
| 863 | global $wpdb; |
| 864 | return (int)$wpdb->get_var(" |
| 865 | SELECT COUNT(*) |
| 866 | FROM {$wpdb->termmeta} tm |
| 867 | LEFT JOIN {$wpdb->terms} t ON tm.term_id = t.term_id |
| 868 | WHERE t.term_id IS NULL |
| 869 | "); |
| 870 | } |
| 871 | |
| 872 | private function count_expired_transients() { |
| 873 | global $wpdb; |
| 874 | return (int)$wpdb->get_var(" |
| 875 | SELECT COUNT(*) |
| 876 | FROM {$wpdb->options} |
| 877 | WHERE option_name LIKE '_transient_timeout_%' |
| 878 | AND option_value < UNIX_TIMESTAMP() |
| 879 | "); |
| 880 | } |
| 881 | |
| 882 | private function count_post_revisions() { |
| 883 | global $wpdb; |
| 884 | return (int)$wpdb->get_var(" |
| 885 | SELECT COUNT(*) |
| 886 | FROM {$wpdb->posts} |
| 887 | WHERE post_type = 'revision' |
| 888 | "); |
| 889 | } |
| 890 | |
| 891 | private function count_auto_drafts() { |
| 892 | global $wpdb; |
| 893 | return (int)$wpdb->get_var(" |
| 894 | SELECT COUNT(*) |
| 895 | FROM {$wpdb->posts} |
| 896 | WHERE post_status = 'auto-draft' |
| 897 | "); |
| 898 | } |
| 899 | |
| 900 | private function count_trashed_posts() { |
| 901 | global $wpdb; |
| 902 | return (int)$wpdb->get_var(" |
| 903 | SELECT COUNT(*) |
| 904 | FROM {$wpdb->posts} |
| 905 | WHERE post_status = 'trash' |
| 906 | "); |
| 907 | } |
| 908 | |
| 909 | private function count_spam_comments() { |
| 910 | global $wpdb; |
| 911 | return (int)$wpdb->get_var(" |
| 912 | SELECT COUNT(*) |
| 913 | FROM {$wpdb->comments} |
| 914 | WHERE comment_approved = 'spam' |
| 915 | "); |
| 916 | } |
| 917 | |
| 918 | private function count_trashed_comments() { |
| 919 | global $wpdb; |
| 920 | return (int)$wpdb->get_var(" |
| 921 | SELECT COUNT(*) |
| 922 | FROM {$wpdb->comments} |
| 923 | WHERE comment_approved = 'trash' |
| 924 | "); |
| 925 | } |
| 926 | |
| 927 | // Clean methods |
| 928 | private function clean_orphaned_postmeta() { |
| 929 | global $wpdb; |
| 930 | return (int)$wpdb->query(" |
| 931 | DELETE pm FROM {$wpdb->postmeta} pm |
| 932 | LEFT JOIN {$wpdb->posts} p ON pm.post_id = p.ID |
| 933 | WHERE p.ID IS NULL |
| 934 | "); |
| 935 | } |
| 936 | |
| 937 | private function clean_orphaned_commentmeta() { |
| 938 | global $wpdb; |
| 939 | return (int)$wpdb->query(" |
| 940 | DELETE cm FROM {$wpdb->commentmeta} cm |
| 941 | LEFT JOIN {$wpdb->comments} c ON cm.comment_id = c.comment_ID |
| 942 | WHERE c.comment_ID IS NULL |
| 943 | "); |
| 944 | } |
| 945 | |
| 946 | private function clean_orphaned_usermeta() { |
| 947 | global $wpdb; |
| 948 | return (int)$wpdb->query(" |
| 949 | DELETE um FROM {$wpdb->usermeta} um |
| 950 | LEFT JOIN {$wpdb->users} u ON um.user_id = u.ID |
| 951 | WHERE u.ID IS NULL |
| 952 | "); |
| 953 | } |
| 954 | |
| 955 | private function clean_orphaned_termmeta() { |
| 956 | global $wpdb; |
| 957 | return (int)$wpdb->query(" |
| 958 | DELETE tm FROM {$wpdb->termmeta} tm |
| 959 | LEFT JOIN {$wpdb->terms} t ON tm.term_id = t.term_id |
| 960 | WHERE t.term_id IS NULL |
| 961 | "); |
| 962 | } |
| 963 | |
| 964 | private function clean_expired_transients() { |
| 965 | global $wpdb; |
| 966 | |
| 967 | $time = time(); |
| 968 | $transients = $wpdb->get_col(" |
| 969 | SELECT option_name |
| 970 | FROM {$wpdb->options} |
| 971 | WHERE option_name LIKE '_transient_timeout_%' |
| 972 | AND option_value < {$time} |
| 973 | "); |
| 974 | |
| 975 | $deleted = 0; |
| 976 | |
| 977 | foreach ($transients as $transient) { |
| 978 | $key = str_replace('_transient_timeout_', '', $transient); |
| 979 | delete_transient($key); |
| 980 | $deleted++; |
| 981 | } |
| 982 | |
| 983 | return $deleted; |
| 984 | } |
| 985 | |
| 986 | private function clean_post_revisions() { |
| 987 | global $wpdb; |
| 988 | return (int)$wpdb->query(" |
| 989 | DELETE FROM {$wpdb->posts} |
| 990 | WHERE post_type = 'revision' |
| 991 | "); |
| 992 | } |
| 993 | |
| 994 | private function clean_auto_drafts() { |
| 995 | global $wpdb; |
| 996 | return (int)$wpdb->query(" |
| 997 | DELETE FROM {$wpdb->posts} |
| 998 | WHERE post_status = 'auto-draft' |
| 999 | "); |
| 1000 | } |
| 1001 | |
| 1002 | private function clean_trashed_posts() { |
| 1003 | global $wpdb; |
| 1004 | return (int)$wpdb->query(" |
| 1005 | DELETE FROM {$wpdb->posts} |
| 1006 | WHERE post_status = 'trash' |
| 1007 | "); |
| 1008 | } |
| 1009 | |
| 1010 | private function clean_spam_comments() { |
| 1011 | global $wpdb; |
| 1012 | return (int)$wpdb->query(" |
| 1013 | DELETE FROM {$wpdb->comments} |
| 1014 | WHERE comment_approved = 'spam' |
| 1015 | "); |
| 1016 | } |
| 1017 | |
| 1018 | private function clean_trashed_comments() { |
| 1019 | global $wpdb; |
| 1020 | return (int)$wpdb->query(" |
| 1021 | DELETE FROM {$wpdb->comments} |
| 1022 | WHERE comment_approved = 'trash' |
| 1023 | "); |
| 1024 | } |
| 1025 | } |
| 1026 | |
| 1027 | // Initialize the plugin |
| 1028 | function db_optimizer_pro_init() { |
| 1029 | new DB_Optimizer_Pro(); |
| 1030 | } |
| 1031 | add_action('plugins_loaded', 'db_optimizer_pro_init'); |