/** * WordPress Bloat Control — unified read/write panel. * * UNIFICATION SURFACE ONLY. This component reuses the existing settings * sources and runtime logic — it registers NO WordPress hooks of its own: * * - Advanced settings : GET/POST /prorank-seo/v1/advanced-settings (prorank_seo_advanced_settings) * - Head cleanup : GET/POST /prorank-seo/v1/settings/head_cleanup (prorank_seo_head_cleanup) * - Heartbeat (DB) : GET /prorank-seo/v1/settings/database (prorank_database_settings, read-only here) * * The "Safe Preset" is strictly non-destructive and CLIENT-SIDE: it flips * only the SAFE-group keys and saves via the Advanced/Head Cleanup write * endpoints above. It deliberately does NOT call * /prorank-seo/v1/performance/presets/bloat-cleanup (that endpoint also * enables scheduled DB cleanup of revisions/spam/transients and changes * heartbeat — out of scope for a bloat panel). * * Runtime hook logic lives in AdvancedSettingsController::apply_settings_internal(), * HeadCleanupModule::setup_head_cleanup() and HeartbeatControlModule — untouched. */ import { useState, useEffect, useCallback } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import apiFetch from '@wordpress/api-fetch'; type Source = 'advanced' | 'head'; interface Toggle { key: string; source: Source; label: string; help?: string; risky?: boolean; riskyNote?: string; } interface Group { id: string; title: string; intro?: string; risky?: boolean; toggles: Toggle[]; } // Grouping derived from the audited inventory. Keys map 1:1 to existing // option keys — no new settings are introduced here. const GROUPS: Group[] = [ { id: 'safe', title: __('Safe cleanup', 'prorank-seo'), intro: __('Pure head-noise removal with no integration impact. These — and only these — are applied by the Safe Preset.', 'prorank-seo'), toggles: [ { key: 'disable_emojis', source: 'advanced', label: __('Disable emojis', 'prorank-seo') }, { key: 'remove_wp_generator', source: 'head', label: __('Remove WP generator tag', 'prorank-seo') }, { key: 'remove_rsd_link', source: 'head', label: __('Remove RSD link', 'prorank-seo') }, { key: 'remove_wlwmanifest_link', source: 'head', label: __('Remove WLW manifest link', 'prorank-seo') }, { key: 'remove_wp_shortlink', source: 'head', label: __('Remove shortlink', 'prorank-seo') }, { key: 'remove_emoji_scripts', source: 'head', label: __('Remove emoji scripts', 'prorank-seo') }, { key: 'disable_self_pingbacks', source: 'head', label: __('Disable self-pingbacks', 'prorank-seo') }, { key: 'remove_comment_urls', source: 'head', label: __('Remove comment author URLs', 'prorank-seo') }, ], }, { id: 'head', title: __('WordPress head cleanup (review first)', 'prorank-seo'), intro: __('Reasonable removals that CAN affect integrations, caching plugins, or RSS/embed consumers. Not part of the Safe Preset — enable individually after reviewing.', 'prorank-seo'), toggles: [ { key: 'disable_xmlrpc', source: 'advanced', label: __('Disable XML-RPC', 'prorank-seo'), help: __('Breaks the Jetpack/mobile app/remote-publishing API and some security plugins that rely on xmlrpc.php.', 'prorank-seo') }, { key: 'remove_version_strings', source: 'advanced', label: __('Remove version query strings', 'prorank-seo'), help: __('Can interfere with some CDN/cache-busting setups that key on ?ver=.', 'prorank-seo') }, { key: 'remove_rest_api_links', source: 'head', label: __('Remove REST API discovery links', 'prorank-seo'), help: __('Hides REST autodiscovery — some integrations/headless tooling rely on it.', 'prorank-seo') }, { key: 'remove_oembed_links', source: 'head', label: __('Remove oEmbed discovery links', 'prorank-seo'), help: __('Other sites can no longer auto-embed your posts.', 'prorank-seo') }, { key: 'remove_dns_prefetch', source: 'head', label: __('Remove s.w.org DNS-prefetch', 'prorank-seo') }, { key: 'remove_version_numbers', source: 'head', label: __('Strip ?ver from assets', 'prorank-seo'), help: __('ProRank/Elementor build assets are excluded automatically.', 'prorank-seo') }, { key: 'remove_adjacent_posts_links', source: 'head', label: __('Remove adjacent-post links', 'prorank-seo') }, { key: 'limit_post_revisions', source: 'head', label: __('Limit post revisions', 'prorank-seo'), help: __('Revision cap configured in Technical SEO → Head Cleanup.', 'prorank-seo') }, ], }, { id: 'woocommerce', title: __('WooCommerce bloat', 'prorank-seo'), intro: __('Only relevant on WooCommerce stores. Asset removal uses store-page detection but test checkout/cart after enabling.', 'prorank-seo'), risky: true, toggles: [ { key: 'disable_woocommerce_scripts', source: 'advanced', label: __('Dequeue WooCommerce assets off-store', 'prorank-seo'), risky: true, riskyNote: __('Uses a store-page heuristic — verify product/cart/checkout still render.', 'prorank-seo') }, { key: 'disable_woocommerce_cart_fragments', source: 'advanced', label: __('Disable cart fragments', 'prorank-seo'), risky: true, riskyNote: __('Removes the AJAX cart-count request; mini-cart may not live-update on some themes.', 'prorank-seo') }, { key: 'disable_woocommerce_widgets', source: 'advanced', label: __('Unregister WooCommerce widgets', 'prorank-seo'), risky: true, riskyNote: __('Removes 15 WC widgets — only if you do not use them.', 'prorank-seo') }, { key: 'disable_woocommerce_status_meta_box', source: 'advanced', label: __('Remove WC dashboard widgets', 'prorank-seo') }, { key: 'disable_password_strength_meter', source: 'advanced', label: __('Disable password strength meter', 'prorank-seo') }, ], }, { id: 'risky', title: __('Risky / compatibility-sensitive', 'prorank-seo'), intro: __('These can break themes, plugins or integrations. Enable one at a time and test.', 'prorank-seo'), risky: true, toggles: [ { key: 'remove_jquery_migrate', source: 'head', label: __('Remove jQuery Migrate', 'prorank-seo'), risky: true, riskyNote: __('Older themes/plugins relying on deprecated jQuery APIs may break.', 'prorank-seo') }, { key: 'disable_rest_api_public', source: 'advanced', label: __('Restrict REST API (logged-out)', 'prorank-seo'), risky: true, riskyNote: __('Breaks headless front-ends, some block features and 3rd-party integrations.', 'prorank-seo') }, { key: 'remove_feed_links', source: 'head', label: __('Remove feed links', 'prorank-seo'), risky: true, riskyNote: __('Breaks RSS discovery for readers/aggregators (e.g. Mailchimp RSS, Apple News).', 'prorank-seo') }, { key: 'remove_feed_links_extra', source: 'head', label: __('Remove extra feed links', 'prorank-seo'), risky: true, riskyNote: __('Removes comment/category feed links.', 'prorank-seo') }, { key: 'disable_gutenberg', source: 'advanced', label: __('Disable Gutenberg (block editor)', 'prorank-seo'), risky: true, riskyNote: __('Forces the classic editor and dequeues block CSS — changes the editing experience site-wide.', 'prorank-seo') }, { key: 'disable_embeds', source: 'head', label: __('Disable embeds', 'prorank-seo'), risky: true, riskyNote: __('Internal/oEmbed embeds (incl. some block embeds) stop working.', 'prorank-seo') }, { key: 'remove_global_styles', source: 'advanced', label: __('Remove WP global styles', 'prorank-seo'), risky: true, riskyNote: __('Block-theme / theme.json sites can lose styling.', 'prorank-seo') }, { key: 'disable_dashicons', source: 'advanced', label: __('Disable Dashicons (logged-out)', 'prorank-seo'), risky: true, riskyNote: __('Some themes/plugins use Dashicons on the front end.', 'prorank-seo') }, ], }, ]; const SAFE_KEYS = new Set(GROUPS.find((g) => g.id === 'safe')!.toggles.map((t) => t.key)); const RISKY_KEYS = new Set( GROUPS.flatMap((g) => g.toggles).filter((t) => t.risky).map((t) => t.key) ); const ALL_TOGGLES = GROUPS.flatMap((g) => g.toggles); const BloatControlPanel: React.FC = () => { const [advanced, setAdvanced] = useState>({}); const [head, setHead] = useState>({}); const [heartbeat, setHeartbeat] = useState<{ heartbeat_control?: boolean; heartbeat_frequency?: number }>({}); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [applyingPreset, setApplyingPreset] = useState(false); const [error, setError] = useState(''); const [notice, setNotice] = useState(''); const loadAll = useCallback(async () => { setLoading(true); setError(''); try { const [adv, hd, db] = await Promise.all([ apiFetch<{ data?: Record }>({ path: '/prorank-seo/v1/advanced-settings' }), apiFetch<{ settings?: Record }>({ path: '/prorank-seo/v1/settings/head_cleanup' }), apiFetch>({ path: '/prorank-seo/v1/settings/database' }).catch(() => ({})), ]); setAdvanced((adv?.data as Record) || {}); setHead((hd?.settings as Record) || {}); const dbObj = (db as Record) || {}; setHeartbeat({ heartbeat_control: Boolean(dbObj.heartbeat_control), heartbeat_frequency: Number(dbObj.heartbeat_frequency ?? 60), }); } catch (e) { setError((e as Error)?.message || __('Failed to load bloat settings.', 'prorank-seo')); } finally { setLoading(false); } }, []); useEffect(() => { loadAll(); }, [loadAll]); const valueOf = (t: Toggle): boolean => Boolean(t.source === 'advanced' ? advanced[t.key] : head[t.key]); const setValue = (t: Toggle, next: boolean) => { if (t.source === 'advanced') { setAdvanced((s) => ({ ...s, [t.key]: next })); } else { setHead((s) => ({ ...s, [t.key]: next })); } }; const onToggle = (t: Toggle) => { const next = !valueOf(t); if (next && t.risky) { const ok = window.confirm( `${t.label}\n\n${t.riskyNote || __('This is a compatibility-sensitive change.', 'prorank-seo')}\n\n${__('Enable it anyway?', 'prorank-seo')}` ); if (!ok) return; } setValue(t, next); }; const saveAll = async () => { setSaving(true); setError(''); setNotice(''); try { // One POST per source — server-side merge keeps untouched keys intact. await apiFetch({ path: '/prorank-seo/v1/advanced-settings', method: 'POST', data: { advanced }, }); await apiFetch({ path: '/prorank-seo/v1/settings/head_cleanup', method: 'POST', data: head, }); setNotice(__('Bloat control settings saved.', 'prorank-seo')); await loadAll(); } catch (e) { setError((e as Error)?.message || __('Save failed.', 'prorank-seo')); } finally { setSaving(false); } }; /** * Apply the Safe Preset — STRICTLY non-destructive. * * This intentionally does NOT call /performance/presets/bloat-cleanup * (that endpoint also enables scheduled DB cleanup of revisions / spam / * transients and changes heartbeat — out of scope for a bloat panel). * Instead it only flips the SAFE group keys on and saves via the same * Advanced/Head Cleanup write endpoints the manual toggles use. No DB * cleanup, no heartbeat changes, existing risky toggles untouched. */ const applySafePreset = async () => { if ( !window.confirm( __('Apply the Safe Preset? This only enables the pure head-noise cleanups in the “Safe cleanup” group (no XML-RPC/REST/version changes, no jQuery Migrate, no Gutenberg/Woo changes, no database cleanup, no heartbeat changes). Existing toggles outside the Safe group are left exactly as they are.', 'prorank-seo') ) ) { return; } setApplyingPreset(true); setError(''); setNotice(''); try { const safeGroup = GROUPS.find((g) => g.id === 'safe')!; const nextAdvanced: Record = { ...advanced }; const nextHead: Record = { ...head }; let touchedAdvanced = false; let touchedHead = false; for (const t of safeGroup.toggles) { if (t.source === 'advanced') { if (nextAdvanced[t.key] !== true) { nextAdvanced[t.key] = true; touchedAdvanced = true; } } else if (nextHead[t.key] !== true) { nextHead[t.key] = true; touchedHead = true; } } if (touchedAdvanced) { await apiFetch({ path: '/prorank-seo/v1/advanced-settings', method: 'POST', data: { advanced: nextAdvanced }, }); } if (touchedHead) { await apiFetch({ path: '/prorank-seo/v1/settings/head_cleanup', method: 'POST', data: nextHead, }); } setNotice(__('Safe Preset applied (head-noise cleanups only — no database or heartbeat changes).', 'prorank-seo')); await loadAll(); } catch (e) { setError((e as Error)?.message || __('Failed to apply Safe Preset.', 'prorank-seo')); } finally { setApplyingPreset(false); } }; // Status summary (client-side, from loaded state). // "safe" = ONLY the SAFE_KEYS group. Non-safe, non-risky toggles // (XML-RPC, REST/oEmbed links, version strings, revision limit, etc.) // are counted separately as "review active" — they are not safe. const summary = (() => { let safeActive = 0; let reviewActive = 0; let riskyActive = 0; let inactive = 0; for (const t of ALL_TOGGLES) { const on = Boolean(t.source === 'advanced' ? advanced[t.key] : head[t.key]); if (!on) { inactive += 1; continue; } if (RISKY_KEYS.has(t.key)) { riskyActive += 1; } else if (SAFE_KEYS.has(t.key)) { safeActive += 1; } else { reviewActive += 1; } } return { safeActive, reviewActive, riskyActive, inactive }; })(); return (

{__('WordPress Bloat Control', 'prorank-seo')}

{__( 'A unified view of cleanup controls that already exist under Support & Account → Advanced Settings and Technical SEO → Head Cleanup. Changes here write to the same options and use the same runtime logic.', 'prorank-seo' )}
{loading && (
{__('Loading bloat settings…', 'prorank-seo')}
)} {!loading && error && (
{error}
)} {!loading && !error && ( <> {/* Status summary */}
{summary.safeActive} {__('safe active', 'prorank-seo')}
{summary.reviewActive} {__('review active', 'prorank-seo')}
{summary.riskyActive} {__('risky active', 'prorank-seo')}
{summary.inactive} {__('inactive opportunities', 'prorank-seo')}
{heartbeat.heartbeat_control && (
{__('heartbeat throttled', 'prorank-seo')} ({heartbeat.heartbeat_frequency}s)
)}
{notice && (
{notice}
)} {GROUPS.map((group) => (
{group.title} {group.risky && ( {__('⚠ review before enabling', 'prorank-seo')} )}
{group.intro && (
{group.intro}
)}
{group.toggles.map((t) => { const on = valueOf(t); return ( ); })}
))}
{__( 'Same controls also live under Support & Account → Advanced Settings and Technical SEO → Head Cleanup. Heartbeat is shown read-only here and managed in Database & Server settings — the Safe Preset does NOT change it.', 'prorank-seo' )}
)}
); }; export default BloatControlPanel;