Naposledy aktivní 1 month ago

Revize 2eec938cf002ab94fd56697f968bad17ddfd398c

database.png Raw
database.png
gistfile1.txt Raw
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
13if (!defined('ABSPATH')) {
14 exit;
15}
16
17class 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
1028function db_optimizer_pro_init() {
1029 new DB_Optimizer_Pro();
1030}
1031add_action('plugins_loaded', 'db_optimizer_pro_init');