PluginProbe ʕ •ᴥ•ʔ
Autoload Optimizer / trunk
Autoload Optimizer vtrunk
2.1 trunk 1.0 2.0
autoload-optimizer / autoload-optimizer.php
autoload-optimizer Last commit date
assets 1 day ago autoload-optimizer.php 1 day ago readme.txt 1 day ago
autoload-optimizer.php
547 lines
1 <?php
2 /*
3 Plugin Name: Autoload Optimizer
4 Version: 2.1
5 Description: Checks the autoloaded data size and lists the top autoloaded data entries sorted by size.
6 Author: WP Fix Experts
7 Author URI: https://wpfixexperts.com
8 License: GPLv2 or later
9 License URI: https://www.gnu.org/licenses/gpl-2.0.html
10 Text Domain: autoload-optimizer
11 */
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 define( 'ALM_VERSION', '2.1.0' );
18 define( 'ALM_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
19 define( 'ALM_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
20
21 class Autoload_Optimizer {
22
23 public function __construct() {
24 add_action( 'admin_menu', array( $this, 'register_menu' ) );
25 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
26 add_action( 'wp_ajax_alm_toggle_autoload', array( $this, 'ajax_toggle_autoload' ) );
27 add_action( 'wp_ajax_alm_auto_fix_autoload', array( $this, 'ajax_auto_fix_autoload' ) );
28 add_action( 'admin_notices', array( $this, 'suppress_notices' ), 1 );
29 add_action( 'all_admin_notices', array( $this, 'suppress_notices' ), 1 );
30 add_action( 'network_admin_notices', array( $this, 'suppress_notices' ), 1 );
31 add_action( 'user_admin_notices', array( $this, 'suppress_notices' ), 1 );
32 add_action( 'admin_head', array( $this, 'suppress_notices_css' ) );
33 }
34
35 public function suppress_notices() {
36 if ( ! $this->is_our_page() ) {
37 return;
38 }
39 remove_all_actions( 'admin_notices' );
40 remove_all_actions( 'all_admin_notices' );
41 remove_all_actions( 'network_admin_notices' );
42 remove_all_actions( 'user_admin_notices' );
43 }
44
45 public function suppress_notices_css() {
46 if ( ! $this->is_our_page() ) {
47 return;
48 }
49 echo '<style>
50 /* Hide all admin notices on the Autoload Optimizer page */
51 #wpbody-content .notice,
52 #wpbody-content .notice-success,
53 #wpbody-content .notice-error,
54 #wpbody-content .notice-warning,
55 #wpbody-content .notice-info,
56 #wpbody-content .updated,
57 #wpbody-content .update-nag,
58 #wpbody-content .error,
59 .wrap > .notice,
60 .wrap > .updated,
61 .wrap > .error,
62 .wrap > .update-nag {
63 display: none !important;
64 }
65 </style>';
66 }
67
68 private function is_our_page() {
69 $screen = get_current_screen();
70 return $screen && $screen->id === 'tools_page_autoload-optimizer';
71 }
72
73 public function register_menu() {
74 add_management_page(
75 __( 'Autoload Optimizer', 'autoload-optimizer' ),
76 __( 'Autoload Optimizer', 'autoload-optimizer' ),
77 'manage_options',
78 'autoload-optimizer',
79 array( $this, 'render_page' )
80 );
81 }
82
83 public function enqueue_assets( $hook ) {
84 if ( $hook !== 'tools_page_autoload-optimizer' ) {
85 return;
86 }
87 wp_enqueue_style( 'alm-style', ALM_PLUGIN_URL . 'assets/style.css', array(), ALM_VERSION );
88 wp_enqueue_script( 'alm-script', ALM_PLUGIN_URL . 'assets/script.js', array( 'jquery' ), ALM_VERSION, true );
89 wp_localize_script( 'alm-script', 'almData', array(
90 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
91 'nonce' => wp_create_nonce( 'alm_toggle_autoload' ),
92 'autoFixNonce' => wp_create_nonce( 'alm_auto_fix_autoload' ),
93 ) );
94 }
95
96 /**
97 * Get top 50 autoloaded options sorted by data length (largest first).
98 */
99 private function get_top_autoload_options() {
100 global $wpdb;
101 $results = $wpdb->get_results(
102 "SELECT option_name, option_value, autoload,
103 LENGTH(option_value) AS size
104 FROM {$wpdb->options}
105 WHERE autoload NOT IN ('no', 'off', 'auto-off')
106 ORDER BY size DESC
107 LIMIT 50",
108 ARRAY_A
109 );
110 return $results ?: array();
111 }
112
113 /**
114 * Get top 50 options that have been disabled (autoload = 'no') by this plugin.
115 */
116 private function get_disabled_options() {
117 global $wpdb;
118 $disabled_keys = get_option( 'alm_disabled_options', array() );
119 if ( empty( $disabled_keys ) ) {
120 return array();
121 }
122 $placeholders = implode( ',', array_fill( 0, count( $disabled_keys ), '%s' ) );
123 $results = $wpdb->get_results(
124 $wpdb->prepare(
125 "SELECT option_name, option_value, autoload, LENGTH(option_value) AS size
126 FROM {$wpdb->options}
127 WHERE option_name IN ($placeholders)
128 ORDER BY size DESC",
129 $disabled_keys
130 ),
131 ARRAY_A
132 );
133 return $results ?: array();
134 }
135
136 /**
137 * Try to map an option name to a plugin name using common prefixes.
138 */
139 private function guess_plugin_for_option( $option_name ) {
140 $plugins = get_plugins();
141 $best = null;
142 $best_len = 0;
143
144 foreach ( $plugins as $file => $data ) {
145 $slug = dirname( $file );
146 if ( $slug === '.' ) {
147 $slug = basename( $file, '.php' );
148 }
149 $slug_clean = strtolower( str_replace( array( '-', '_', ' ' ), '', $slug ) );
150 $option_clean = strtolower( str_replace( array( '-', '_', ' ' ), '', $option_name ) );
151
152 if ( strlen( $slug_clean ) >= 3 && strpos( $option_clean, $slug_clean ) !== false ) {
153 if ( strlen( $slug_clean ) > $best_len ) {
154 $best = $data['Name'];
155 $best_len = strlen( $slug_clean );
156 }
157 }
158 }
159 return $best;
160 }
161
162 /**
163 * Build the WHERE clause + params matching transient-related option names
164 * ( _transient_*, _site_transient_*, _transient_timeout_*, _site_transient_timeout_* ).
165 */
166 private function build_transient_where( $only_autoloaded = false ) {
167 global $wpdb;
168 $prefixes = array( '_transient_', '_site_transient_', '_transient_timeout_', '_site_transient_timeout_' );
169 $clauses = array();
170 $params = array();
171 foreach ( $prefixes as $prefix ) {
172 $clauses[] = 'option_name LIKE %s';
173 $params[] = $wpdb->esc_like( $prefix ) . '%';
174 }
175 $where = '(' . implode( ' OR ', $clauses ) . ')';
176 if ( $only_autoloaded ) {
177 $where .= " AND autoload NOT IN ('no', 'off', 'auto-off')";
178 }
179 return array( $where, $params );
180 }
181
182 /**
183 * Get names of transient-related options, optionally limited to ones still autoloaded.
184 */
185 private function get_transient_option_names( $only_autoloaded = false ) {
186 global $wpdb;
187 list( $where, $params ) = $this->build_transient_where( $only_autoloaded );
188 $sql = "SELECT option_name FROM {$wpdb->options} WHERE {$where}";
189 return $wpdb->get_col( $wpdb->prepare( $sql, $params ) );
190 }
191
192 /**
193 * Count how many transient-related options are currently autoloaded.
194 */
195 private function count_transient_autoloaded() {
196 return count( $this->get_transient_option_names( true ) );
197 }
198
199 /**
200 * Format bytes to human-readable string.
201 */
202 private function format_bytes( $bytes ) {
203 if ( $bytes >= 1073741824 ) {
204 return number_format( $bytes / 1073741824, 2 ) . ' GB';
205 } elseif ( $bytes >= 1048576 ) {
206 return number_format( $bytes / 1048576, 2 ) . ' MB';
207 } elseif ( $bytes >= 1024 ) {
208 return number_format( $bytes / 1024, 2 ) . ' KB';
209 }
210 return $bytes . ' B';
211 }
212
213 /**
214 * AJAX handler to toggle autoload status.
215 */
216 public function ajax_toggle_autoload() {
217 check_ajax_referer( 'alm_toggle_autoload', 'nonce' );
218
219 if ( ! current_user_can( 'manage_options' ) ) {
220 wp_send_json_error( array( 'message' => 'Permission denied.' ) );
221 }
222
223 $option_name = isset( $_POST['option_name'] ) ? sanitize_text_field( wp_unslash( $_POST['option_name'] ) ) : '';
224 $action_type = isset( $_POST['action_type'] ) ? sanitize_text_field( wp_unslash( $_POST['action_type'] ) ) : '';
225
226 if ( empty( $option_name ) || ! in_array( $action_type, array( 'disable', 'enable' ), true ) ) {
227 wp_send_json_error( array( 'message' => 'Invalid request.' ) );
228 }
229
230 global $wpdb;
231
232 if ( $action_type === 'disable' ) {
233 $new_autoload = 'no';
234 $disabled_keys = get_option( 'alm_disabled_options', array() );
235 if ( ! in_array( $option_name, $disabled_keys, true ) ) {
236 $disabled_keys[] = $option_name;
237 update_option( 'alm_disabled_options', $disabled_keys );
238 }
239 } else {
240 $new_autoload = 'yes';
241 $disabled_keys = get_option( 'alm_disabled_options', array() );
242 $disabled_keys = array_values( array_diff( $disabled_keys, array( $option_name ) ) );
243 update_option( 'alm_disabled_options', $disabled_keys );
244 }
245
246 $updated = $wpdb->update(
247 $wpdb->options,
248 array( 'autoload' => $new_autoload ),
249 array( 'option_name' => $option_name ),
250 array( '%s' ),
251 array( '%s' )
252 );
253
254 if ( $updated !== false ) {
255 wp_send_json_success( array(
256 'message' => $action_type === 'disable'
257 ? 'Autoload disabled for: ' . esc_html( $option_name )
258 : 'Autoload re-enabled for: ' . esc_html( $option_name ),
259 'action_type' => $action_type,
260 ) );
261 } else {
262 wp_send_json_error( array( 'message' => 'Database update failed.' ) );
263 }
264 }
265
266 /**
267 * AJAX handler: bulk-disable autoload for _transient_*, _site_transient_*,
268 * _transient_timeout_* and _site_transient_timeout_* options.
269 */
270 public function ajax_auto_fix_autoload() {
271 check_ajax_referer( 'alm_auto_fix_autoload', 'nonce' );
272
273 if ( ! current_user_can( 'manage_options' ) ) {
274 wp_send_json_error( array( 'message' => 'Permission denied.' ) );
275 }
276
277 global $wpdb;
278
279 $names = $this->get_transient_option_names( true );
280
281 if ( empty( $names ) ) {
282 wp_send_json_success( array(
283 'message' => 'No autoloaded transient options were found. Nothing to fix.',
284 'count' => 0,
285 ) );
286 }
287
288 list( $where, $params ) = $this->build_transient_where( true );
289 $sql = "UPDATE {$wpdb->options} SET autoload = 'no' WHERE {$where}";
290 $wpdb->query( $wpdb->prepare( $sql, $params ) );
291
292 $disabled_keys = get_option( 'alm_disabled_options', array() );
293 $disabled_keys = array_values( array_unique( array_merge( $disabled_keys, $names ) ) );
294 update_option( 'alm_disabled_options', $disabled_keys );
295
296 wp_send_json_success( array(
297 'message' => sprintf( 'Auto Fix complete: autoload disabled for %d transient option(s).', count( $names ) ),
298 'count' => count( $names ),
299 ) );
300 }
301
302 public function render_page() {
303 if ( ! current_user_can( 'manage_options' ) ) {
304 return;
305 }
306
307 $top_options = $this->get_top_autoload_options();
308 $disabled_options = $this->get_disabled_options();
309
310 global $wpdb;
311 $total_autoload_size = (int) $wpdb->get_var(
312 "SELECT SUM(LENGTH(option_value)) FROM {$wpdb->options}
313 WHERE autoload NOT IN ('no', 'off', 'auto-off')"
314 );
315
316 $total_db_size = (int) $wpdb->get_var(
317 $wpdb->prepare(
318 "SELECT SUM(data_length + index_length)
319 FROM information_schema.TABLES
320 WHERE table_schema = %s",
321 DB_NAME
322 )
323 );
324
325 $disabled_count = count( $disabled_options );
326 $transient_count = $this->count_transient_autoloaded();
327 ?>
328 <div class="wrap alm-wrap">
329
330 <!-- Header -->
331 <div class="alm-header">
332 <div class="alm-header-title">
333 <span class="dashicons dashicons-performance alm-header-icon"></span>
334 <div>
335 <h1>Autoload Optimizer</h1>
336 <p>This powerful tool helps you optimize and manage autoloaded data in your WordPress database, improving site performance and speed. With just a few clicks, you can identify and clean up unnecessary autoloaded entries, reducing database bloat and enhancing website efficiency.</p>
337 </div>
338 </div>
339 <div class="alm-stat-badges">
340 <div class="alm-stat-badge">
341 <span class="alm-stat-label">Total Autoload Size</span>
342 <span class="alm-stat-value"><?php echo esc_html( $this->format_bytes( $total_autoload_size ) ); ?></span>
343 </div>
344 <div class="alm-stat-badge alm-stat-badge-db">
345 <span class="alm-stat-label">Total Database Size</span>
346 <span class="alm-stat-value"><?php echo esc_html( $this->format_bytes( $total_db_size ) ); ?></span>
347 </div>
348 </div>
349 </div>
350
351 <!-- Auto Fix -->
352 <div class="alm-autofix-box">
353 <div class="alm-autofix-text">
354 <div class="alm-autofix-title">
355 <span class="dashicons dashicons-admin-tools"></span>
356 Auto Fix Autoload Issues
357 </div>
358 <p>
359 Instantly disable autoload for transient options
360 (<code>_transient_*</code>, <code>_site_transient_*</code>, <code>_transient_timeout_*</code>, <code>_site_transient_timeout_*</code>)
361 &mdash; transients are cached data and should never be autoloaded.
362 <?php if ( $transient_count > 0 ) : ?>
363 <strong><?php echo esc_html( $transient_count ); ?> autoloaded transient option(s) detected right now.</strong>
364 <?php else : ?>
365 No autoloaded transient options detected right now.
366 <?php endif; ?>
367 </p>
368 </div>
369 <button type="button" class="button button-primary button-hero alm-autofix-btn" id="alm-auto-fix-btn">
370 <span class="dashicons dashicons-admin-tools"></span>
371 Auto Fix Autoload
372 </button>
373 </div>
374
375 <!-- Warning notice -->
376 <div class="alm-warning-box">
377 <div class="alm-warning-title">
378 <span class="dashicons dashicons-warning"></span>
379 Warning Note: Proceed with Caution!
380 <span class="dashicons dashicons-warning"></span>
381 </div>
382 <p>The Autoload Optimizer Plugin makes direct modifications to your WordPress database. Disabling essential autoloaded data may cause unexpected issues, including broken functionality or site errors.</p>
383 <div class="alm-warning-checklist">
384 <strong>Before making changes, please:</strong>
385 <ul>
386 <li><span class="dashicons dashicons-yes-alt"></span> Create a full database backup</li>
387 <li><span class="dashicons dashicons-yes-alt"></span> Review the list of autoloaded options carefully</li>
388 <li><span class="dashicons dashicons-yes-alt"></span> Test changes on a staging site if possible</li>
389 </ul>
390 </div>
391 <p>Use this tool responsibly to avoid any disruptions. If you're unsure, consult a developer or seek professional support.</p>
392 <p class="alm-warning-footer">Your website's stability is our priority!</p>
393 </div>
394
395 <!-- Promo / support notice -->
396 <div class="alm-promo-box">
397 <span class="dashicons dashicons-sos alm-promo-icon"></span>
398 <div class="alm-promo-text">
399 <strong>Need help optimizing autoload data?</strong>
400 We can help you fix autoload issues on your site.
401 </div>
402 <a href="https://wpfixexperts.com/wordpress-database-autoload-fix/" target="_blank" rel="noopener noreferrer" class="alm-promo-link">Contact us here</a>
403 </div>
404
405 <!-- Tabs nav -->
406 <div class="alm-tabs-nav">
407 <button class="alm-tab-btn alm-tab-active" data-tab="tab-active">
408 <span class="dashicons dashicons-database"></span>
409 Top 50 Autoloaded
410 <span class="alm-tab-count"><?php echo esc_html( count( $top_options ) ); ?></span>
411 </button>
412 <button class="alm-tab-btn" data-tab="tab-disabled">
413 <span class="dashicons dashicons-hidden"></span>
414 Disabled by Plugin
415 <span class="alm-tab-count" id="alm-disabled-count"><?php echo esc_html( $disabled_count ); ?></span>
416 </button>
417 </div>
418
419 <!-- Tab 1: Top 50 Autoloaded Options -->
420 <div class="alm-tab-panel alm-section" id="tab-active">
421 <div class="alm-section-header">
422 <p class="alm-section-desc">These options are loaded into memory on every WordPress page load. Disable autoload for items that don't need to be loaded on every request.</p>
423 </div>
424
425 <?php if ( empty( $top_options ) ) : ?>
426 <div class="alm-empty">No autoloaded options found.</div>
427 <?php else : ?>
428 <table class="widefat alm-table" id="alm-active-table">
429 <thead>
430 <tr>
431 <th>#</th>
432 <th>Option Name</th>
433 <th>Linked Plugin</th>
434 <th>Size</th>
435 <th>Autoload</th>
436 <th>Action</th>
437 </tr>
438 </thead>
439 <tbody>
440 <?php foreach ( $top_options as $index => $option ) :
441 $plugin_name = $this->guess_plugin_for_option( $option['option_name'] );
442 $size_bytes = (int) $option['size'];
443 $size_class = $size_bytes >= 102400 ? 'alm-size-high' : ( $size_bytes >= 10240 ? 'alm-size-medium' : 'alm-size-low' );
444 ?>
445 <tr class="alm-row" data-option="<?php echo esc_attr( $option['option_name'] ); ?>">
446 <td class="alm-index"><?php echo esc_html( $index + 1 ); ?></td>
447 <td class="alm-option-name">
448 <code><?php echo esc_html( $option['option_name'] ); ?></code>
449 </td>
450 <td class="alm-plugin">
451 <?php if ( $plugin_name ) : ?>
452 <span class="alm-plugin-badge"><?php echo esc_html( $plugin_name ); ?></span>
453 <?php else : ?>
454 <span class="alm-plugin-unknown">WordPress / Unknown</span>
455 <?php endif; ?>
456 </td>
457 <td class="alm-size">
458 <span class="alm-size-pill <?php echo esc_attr( $size_class ); ?>">
459 <?php echo esc_html( $this->format_bytes( $size_bytes ) ); ?>
460 </span>
461 </td>
462 <td class="alm-autoload-status">
463 <span class="alm-badge alm-badge-enabled">yes</span>
464 </td>
465 <td class="alm-action">
466 <button class="button button-secondary alm-disable-btn"
467 data-option="<?php echo esc_attr( $option['option_name'] ); ?>"
468 data-action="disable">
469 Disable Autoload
470 </button>
471 </td>
472 </tr>
473 <?php endforeach; ?>
474 </tbody>
475 </table>
476 <?php endif; ?>
477 </div>
478
479 <!-- Tab 2: Disabled by Plugin -->
480 <div class="alm-tab-panel alm-section alm-section-disabled alm-tab-hidden" id="tab-disabled">
481 <div class="alm-section-header">
482 <p class="alm-section-desc">Options below have had autoload disabled by Autoload Optimizer. You can re-enable them if needed.</p>
483 </div>
484
485 <div id="alm-disabled-wrapper">
486 <?php if ( empty( $disabled_options ) ) : ?>
487 <div class="alm-empty" id="alm-no-disabled">No options have been disabled yet.</div>
488 <?php else : ?>
489 <table class="widefat alm-table alm-table-disabled" id="alm-disabled-table">
490 <thead>
491 <tr>
492 <th>#</th>
493 <th>Option Name</th>
494 <th>Linked Plugin</th>
495 <th>Size</th>
496 <th>Autoload</th>
497 <th>Action</th>
498 </tr>
499 </thead>
500 <tbody>
501 <?php foreach ( $disabled_options as $index => $option ) :
502 $plugin_name = $this->guess_plugin_for_option( $option['option_name'] );
503 $size_bytes = (int) $option['size'];
504 $size_class = $size_bytes >= 102400 ? 'alm-size-high' : ( $size_bytes >= 10240 ? 'alm-size-medium' : 'alm-size-low' );
505 ?>
506 <tr class="alm-row alm-row-disabled" data-option="<?php echo esc_attr( $option['option_name'] ); ?>">
507 <td class="alm-index"><?php echo esc_html( $index + 1 ); ?></td>
508 <td class="alm-option-name">
509 <code><?php echo esc_html( $option['option_name'] ); ?></code>
510 </td>
511 <td class="alm-plugin">
512 <?php if ( $plugin_name ) : ?>
513 <span class="alm-plugin-badge"><?php echo esc_html( $plugin_name ); ?></span>
514 <?php else : ?>
515 <span class="alm-plugin-unknown">WordPress / Unknown</span>
516 <?php endif; ?>
517 </td>
518 <td class="alm-size">
519 <span class="alm-size-pill <?php echo esc_attr( $size_class ); ?>">
520 <?php echo esc_html( $this->format_bytes( $size_bytes ) ); ?>
521 </span>
522 </td>
523 <td class="alm-autoload-status">
524 <span class="alm-badge alm-badge-disabled">no</span>
525 </td>
526 <td class="alm-action">
527 <button class="button button-primary alm-enable-btn"
528 data-option="<?php echo esc_attr( $option['option_name'] ); ?>"
529 data-action="enable">
530 Re-enable Autoload
531 </button>
532 </td>
533 </tr>
534 <?php endforeach; ?>
535 </tbody>
536 </table>
537 <?php endif; ?>
538 </div>
539 </div>
540
541 </div><!-- .alm-wrap -->
542 <?php
543 }
544 }
545
546 new Autoload_Optimizer();
547