<?php /** * Plugin Name: Handle Audit Panel (CSS + JS) * Description: Panel flotante con pestañas para auditar handles de CSS y JS (oculto en editores: Elementor, Bricks, Builderius y Customizer). Visible para admin o con ?audit=1|css|js. Comentarios en HTML incluidos. * Version: 1.2 */ if (!defined('ABSPATH')) exit; /* ---------- Detección de editores / modos especiales ---------- */ function haudit_is_elementor_editor(): bool { // 1) Detección robusta vía API de Elementor if (defined('ELEMENTOR_VERSION') && class_exists('\Elementor\Plugin')) { $inst = \Elementor\Plugin::$instance ?? null; if ($inst && isset($inst->editor) && method_exists($inst->editor, 'is_edit_mode') && $inst->editor->is_edit_mode()) { return true; } } // 2) Detección por query flags típicos if (isset($_GET['elementor-preview']) || isset($_GET['elementor_library']) || isset($_GET['elementor-page-id']) || isset($_GET['elementor'])) { return true; } return false; } function haudit_is_bricks_editor(): bool { // 1) API de Bricks si existe if (defined('BRICKS_VERSION')) { if (function_exists('bricks_is_builder') && bricks_is_builder()) return true; } // 2) Query flags comunes del builder de Bricks if (!empty($_GET['bricks']) || !empty($_GET['bricks_builder']) || (!empty($_GET['action']) && $_GET['action'] === 'bricks_edit')) { return true; } return false; } function haudit_is_builderius_editor(): bool { // Intentos de detección: función/constante y flags de consulta habituales if (defined('BUILDERIUS_VERSION') || function_exists('builderius')) { if (function_exists('builderius_is_editor') && builderius_is_editor()) return true; } if (!empty($_GET['builderius']) || !empty($_GET['builderius_mode']) || !empty($_GET['builderius_template'])) { return true; } return false; } function haudit_is_customize_or_preview(): bool { if (function_exists('is_customize_preview') && is_customize_preview()) return true; // WP Customizer // Preview genérico de posts/páginas if (!empty($_GET['preview']) || !empty($_GET['preview_id'])) return true; return false; } function haudit_is_iframed(): bool { // No podemos saberlo en PHP con fiabilidad. Se refuerza del lado JS. return false; } function haudit_is_any_builder(): bool { return haudit_is_elementor_editor() || haudit_is_bricks_editor() || haudit_is_builderius_editor() || haudit_is_customize_or_preview() || haudit_is_iframed(); } /* ---------- Helpers ---------- */ function haudit_abs_url($src, $base_fallback) { if (!$src) return ''; if (preg_match('#^(?:https?:)?//#', $src) || 0 === strpos($src, 'data:')) return $src; if (0 === strpos($src, '/')) return home_url($src); return rtrim((string)$base_fallback, '/') . '/' . ltrim((string)$src, '/'); } function haudit_can_show_panel(): bool { if (is_admin()) return false; // solo frontend // ¿Override explícito? $audit = isset($_GET['audit']) ? strtolower(sanitize_text_field($_GET['audit'])) : ''; $override_force = ($audit === 'force'); // muestra incluso dentro de editores (útil puntualmente) if (!$override_force && haudit_is_any_builder()) return false; // Mostrar si: admin + manage_options, o hay cualquier ?audit return ( (is_user_logged_in() && current_user_can('manage_options')) || $audit !== '' ); } function haudit_param_default_tab(): string { $val = isset($_GET['audit']) ? strtolower(sanitize_text_field($_GET['audit'])) : ''; if ($val === 'js') return 'js'; return 'css'; // por defecto } /* ---------- Recolección de datos ---------- */ function haudit_collect_styles(): array { global $wp_styles; if (!isset($wp_styles)) return []; $order = array_values(array_unique(array_merge( (array)($wp_styles->done ?? []), (array)($wp_styles->queue ?? []), (array)($wp_styles->to_do ?? []) ))); $base = $wp_styles->base_url ?? ''; $items = []; foreach ($order as $handle) { $reg = $wp_styles->registered[$handle] ?? null; if (!$reg) continue; $href = isset($reg->src) ? haudit_abs_url($reg->src, $base) : ''; $media = !empty($reg->args) ? $reg->args : 'all'; $ver = isset($reg->ver) ? (string)$reg->ver : ''; $deps = !empty($reg->deps) ? implode(',', (array)$reg->deps) : ''; $items[] = [ 'handle' => $handle, 'media' => $media, 'ver' => $ver, 'deps' => $deps, 'href' => $href, ]; } return $items; } function haudit_collect_scripts(): array { global $wp_scripts; if (!isset($wp_scripts)) return []; $order = array_values(array_unique(array_merge( (array)($wp_scripts->done ?? []), (array)($wp_scripts->queue ?? []), (array)($wp_scripts->to_do ?? []) ))); $base = $wp_scripts->base_url ?? ''; $items = []; foreach ($order as $handle) { $reg = $wp_scripts->registered[$handle] ?? null; if (!$reg) continue; $src = isset($reg->src) ? haudit_abs_url($reg->src, $base) : ''; $ver = isset($reg->ver) ? (string)$reg->ver : ''; $deps = !empty($reg->deps) ? implode(',', (array)$reg->deps) : ''; $where = (!empty($reg->extra['group']) && (int)$reg->extra['group'] === 1) ? 'footer' : 'head'; $items[] = [ 'handle' => $handle, 'where' => $where, 'ver' => $ver, 'deps' => $deps, 'src' => $src, ]; } return $items; } /* ---------- Comentarios en HTML (View Source) ---------- */ add_action('wp_footer', function () { if (!haudit_can_show_panel()) return; $styles = haudit_collect_styles(); $scripts = haudit_collect_scripts(); // CSS echo "\n<!-- HANDLE AUDIT (STYLES) START -->\n"; foreach ($styles as $it) { printf("<!-- handle:%s | media:%s | ver:%s | deps:%s | href:%s -->\n", esc_html($it['handle']), esc_html($it['media']), esc_html($it['ver']), esc_html($it['deps'] ?: '-'), esc_url($it['href']) ); } echo "<!-- HANDLE AUDIT (STYLES) END -->\n"; // JS echo "\n<!-- HANDLE AUDIT (SCRIPTS) START -->\n"; foreach ($scripts as $it) { printf("<!-- handle:%s | where:%s | ver:%s | deps:%s | src:%s -->\n", esc_html($it['handle']), esc_html($it['where']), esc_html($it['ver']), esc_html($it['deps'] ?: '-'), esc_url($it['src']) ); } echo "<!-- HANDLE AUDIT (SCRIPTS) END -->\n"; }, 9998); /* ---------- Panel flotante con pestañas ---------- */ add_action('wp_footer', function () { if (!haudit_can_show_panel()) return; $styles = haudit_collect_styles(); $scripts = haudit_collect_scripts(); $default_tab = haudit_param_default_tab(); // 'css' | 'js' $css_active = $default_tab === 'css'; $js_active = !$css_active; ?> <div id="handle-audit-panel" style="position:fixed;right:12px;bottom:12px;z-index:2147483647;background:#0b0b0c;color:#fff;padding:12px 14px;border-radius:10px;box-shadow:0 10px 30px rgba(0,0,0,.5);max-width:95vw;font:12px/1.45 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono','Courier New', monospace;"> <div style="display:flex;align-items:center;gap:10px;margin-bottom:10px;"> <strong style="font-size:13px;">Handle Audit</strong> <span style="opacity:.7;">(CSS: <?= esc_html(count($styles)) ?> • JS: <?= esc_html(count($scripts)) ?>)</span> <span style="margin-left:auto;opacity:.75;">Abrir por defecto: <code>?audit=css</code> o <code>?audit=js</code></span> <a href="#" onclick="document.getElementById('handle-audit-panel').remove();return false;" style="color:#fff;text-decoration:none;font-weight:bold;padding:2px 6px;border:1px solid #333;border-radius:6px;">✕</a> </div> <div style="display:flex;gap:8px;margin-bottom:8px;"> <button data-ha-tab="css" class="ha-tab<?= $css_active ? ' is-active':'' ?>" style="cursor:pointer;background:<?= $css_active ? '#1f2937':'#111' ?>;color:#fff;border:1px solid #333;border-radius:6px;padding:6px 10px;">CSS (<?= esc_html(count($styles)) ?>)</button> <button data-ha-tab="js" class="ha-tab<?= $js_active ? ' is-active':'' ?>" style="cursor:pointer;background:<?= $js_active ? '#1f2937':'#111' ?>;color:#fff;border:1px solid #333;border-radius:6px;padding:6px 10px;">JS (<?= esc_html(count($scripts)) ?>)</button> </div> <!-- Panel CSS --> <div class="ha-panel ha-panel-css" style="<?= $css_active ? '' : 'display:none;' ?>"> <div style="display:flex;gap:8px;align-items:center;margin-bottom:6px;"> <input type="search" placeholder="Filtrar CSS (handle, url, deps, media)..." class="ha-filter ha-filter-css" style="flex:1;min-width:220px;padding:6px 8px;border-radius:6px;border:1px solid #333;background:#0f1115;color:#e5e7eb;" /> <button class="ha-copy ha-copy-css" style="padding:6px 8px;border-radius:6px;border:1px solid #333;background:#111;color:#fff;cursor:pointer;">Copiar handles</button> </div> <div style="max-height:55vh;overflow:auto;border:1px solid #333;border-radius:6px;"> <table class="ha-table ha-table-css" style="border-collapse:collapse;width:100%;min-width:760px;"> <thead> <tr> <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #333;">handle</th> <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #333;">media</th> <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #333;">ver</th> <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #333;">deps</th> <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #333;">href</th> </tr> </thead> <tbody> <?php foreach ($styles as $it): ?> <tr> <td style="padding:6px 8px;border-bottom:1px solid #222;white-space:nowrap;"><?= esc_html($it['handle']) ?></td> <td style="padding:6px 8px;border-bottom:1px solid #222;"><?= esc_html($it['media']) ?></td> <td style="padding:6px 8px;border-bottom:1px solid #222;"><?= esc_html($it['ver']) ?></td> <td style="padding:6px 8px;border-bottom:1px solid #222;"><?= esc_html($it['deps'] ?: '-') ?></td> <td style="padding:6px 8px;border-bottom:1px solid #222;"> <?php if (!empty($it['href'])): ?> <a href="<?= esc_url($it['href']) ?>" target="_blank" rel="noopener" style="color:#93c5fd;word-break:break-all;"><?= esc_html($it['href']) ?></a> <?php else: ?> <em style="color:#bbb;">(inline / sin src)</em> <?php endif; ?> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> <!-- Panel JS --> <div class="ha-panel ha-panel-js" style="<?= $js_active ? '' : 'display:none;' ?>"> <div style="display:flex;gap:8px;align-items:center;margin-bottom:6px;"> <input type="search" placeholder="Filtrar JS (handle, url, deps, head/footer)..." class="ha-filter ha-filter-js" style="flex:1;min-width:220px;padding:6px 8px;border-radius:6px;border:1px solid #333;background:#0f1115;color:#e5e7eb;" /> <button class="ha-copy ha-copy-js" style="padding:6px 8px;border-radius:6px;border:1px solid #333;background:#111;color:#fff;cursor:pointer;">Copiar handles</button> </div> <div style="max-height:55vh;overflow:auto;border:1px solid #333;border-radius:6px;"> <table class="ha-table ha-table-js" style="border-collapse:collapse;width:100%;min-width:760px;"> <thead> <tr> <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #333;">handle</th> <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #333;">head/footer</th> <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #333;">ver</th> <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #333;">deps</th> <th style="text-align:left;padding:6px 8px;border-bottom:1px solid #333;">src</th> </tr> </thead> <tbody> <?php foreach ($scripts as $it): ?> <tr> <td style="padding:6px 8px;border-bottom:1px solid #222;white-space:nowrap;"><?= esc_html($it['handle']) ?></td> <td style="padding:6px 8px;border-bottom:1px solid #222;"><?= esc_html($it['where']) ?></td> <td style="padding:6px 8px;border-bottom:1px solid #222;"><?= esc_html($it['ver']) ?></td> <td style="padding:6px 8px;border-bottom:1px solid #222;"><?= esc_html($it['deps'] ?: '-') ?></td> <td style="padding:6px 8px;border-bottom:1px solid #222;"> <?php if (!empty($it['src'])): ?> <a href="<?= esc_url($it['src']) ?>" target="_blank" rel="noopener" style="color:#93c5fd;word-break:break-all;"><?= esc_html($it['src']) ?></a> <?php else: ?> <em style="color:#bbb;">(inline / sin src)</em> <?php endif; ?> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> </div> <script> (function(){ // Ocultar siempre si estamos dentro de un iframe (típico de editores visuales) try { if (window.self !== window.top) { var p = document.getElementById('handle-audit-panel'); if (p) { p.remove(); } return; } } catch(e) { /* cross-origin: asumimos que es iframe de editor y no mostramos */ var p = document.getElementById('handle-audit-panel'); if (p) { p.remove(); } return; } var root = document.getElementById('handle-audit-panel'); if(!root) return; // Tabs var tabs = root.querySelectorAll('.ha-tab'); var panelCSS = root.querySelector('.ha-panel-css'); var panelJS = root.querySelector('.ha-panel-js'); tabs.forEach(function(btn){ btn.addEventListener('click', function(){ tabs.forEach(function(b){ b.classList.remove('is-active'); b.style.background = '#111'; }); btn.classList.add('is-active'); btn.style.background = '#1f2937'; if (btn.getAttribute('data-ha-tab') === 'css') { panelCSS.style.display = ''; panelJS.style.display = 'none'; } else { panelCSS.style.display = 'none'; panelJS.style.display = ''; } }); }); // Filtro por input function wireFilter(panelSelector) { var panel = root.querySelector(panelSelector); if(!panel) return; var input = panel.querySelector('.ha-filter'); var rows = panel.querySelectorAll('tbody tr'); if(!input) return; input.addEventListener('input', function(){ var q = (input.value || '').toLowerCase(); rows.forEach(function(tr){ var text = tr.textContent.toLowerCase(); tr.style.display = text.indexOf(q) !== -1 ? '' : 'none'; }); }); } wireFilter('.ha-panel-css'); wireFilter('.ha-panel-js'); // Copiar handles visibles function wireCopy(panelSelector, tableSelector, btnSelector) { var panel = root.querySelector(panelSelector); if(!panel) return; var btn = panel.querySelector(btnSelector); var table = panel.querySelector(tableSelector); if(!btn || !table) return; btn.addEventListener('click', function(){ var rows = table.querySelectorAll('tbody tr'); var list = []; rows.forEach(function(tr){ if (tr.style.display === 'none') return; var first = tr.querySelector('td'); if (first) list.push(first.textContent.trim()); }); var text = list.join("\n"); if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(function(){ btn.textContent = '¡Copiado!'; setTimeout(function(){ btn.textContent = 'Copiar handles'; }, 1200); }); } else { var ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); } catch(e){} document.body.removeChild(ta); } }); } wireCopy('.ha-panel-css', '.ha-table-css', '.ha-copy-css'); wireCopy('.ha-panel-js', '.ha-table-js', '.ha-copy-js'); })(); </script> <?php }, 9999);
Panel flotante para auditar handles de CSS y JS en el frontend. Muestra handle, dependencias, versión, ubicación (media/head/footer) y URL; añade filtro de búsqueda y botón “Copiar handles”. Además, imprime un resumen en comentarios HTML (útil en “Ver código fuente”).
Funcionamiento / modos:
manage_options
: visible sin parámetros.?audit=
en la URL.?audit=
):
1
→ activa el panel (pestaña CSS por defecto).css
→ activa y abre CSS por defecto.js
→ activa y abre JS por defecto.force
→ intenta mostrarlo incluso en editores; si el builder usa iframe aislado, puede seguir ocultándose por seguridad.?audit
.