Created
March 17, 2026 05:29
-
-
Save arenagroove/1cf3d9746623b6c9cb1e41ee202f4aa1 to your computer and use it in GitHub Desktop.
LR ACF Suite, MU utility plugin for managing ACF/ACFE behavior across projects.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Plugin Name: LR ACF Suite | |
| * Description: ACF/ACFE management — performance, field-group collapse, permissions and HTML escaping. Configurable via Settings → LR ACF Suite. | |
| * Version: 1.1.1 | |
| * Author: Luis Martinez | |
| * Author URI: https://www.lessrain.com | |
| */ | |
| if ( ! defined( 'ABSPATH' ) ) exit; | |
| // ============================================================================= | |
| // 0 Constants | |
| // ============================================================================= | |
| define( 'LR_ACF_SUITE_VER', '1.1.1' ); | |
| define( 'LR_ACF_SUITE_OPTION', 'lr_acf_suite' ); | |
| define( 'LR_ACF_SUITE_NONCE', 'lr_acf_suite_save' ); | |
| // ============================================================================= | |
| // 1 Settings helpers | |
| // ============================================================================= | |
| function lr_acf_suite_defaults(): array { | |
| return [ | |
| // Performance | |
| 'perf_remove_autop' => 1, | |
| 'perf_remove_autoformat' => 1, | |
| 'perf_disable_media' => 1, | |
| 'perf_disable_shortcodes' => 1, | |
| 'perf_acf_json' => 1, | |
| 'perf_disable_post_types' => 1, | |
| 'perf_disable_options_ui' => 1, | |
| 'perf_disable_field_tabs' => 1, | |
| 'perf_disable_script_translations' => 1, | |
| 'perf_disable_clone_templates' => 1, | |
| // Collapse | |
| 'collapse_enabled' => 1, | |
| 'collapse_groups' => implode( "\n", [ | |
| 'group_67375c2b7e4bb', | |
| 'group_67a0d08aa55b0', | |
| 'group_65681cb6122f8', | |
| 'group_665fe96c7c14a', | |
| 'group_645e45603884f', | |
| 'group_694cd3f4199e5', | |
| ] ), | |
| // Permissions | |
| 'perms_caps_enabled' => 1, | |
| 'perms_user_role_fix' => 1, | |
| // Escaping | |
| 'escape_unsafe_html' => 0, | |
| ]; | |
| } | |
| /** | |
| * Load saved settings merged with defaults. | |
| * Pass $refresh = true to bust the static cache (e.g. after an option update | |
| * within the same request). | |
| */ | |
| function lr_acf_suite_settings( bool $refresh = false ): array { | |
| static $cache = null; | |
| if ( $refresh || $cache === null ) { | |
| $saved = get_option( LR_ACF_SUITE_OPTION, [] ); | |
| $cache = array_merge( lr_acf_suite_defaults(), is_array( $saved ) ? $saved : [] ); | |
| } | |
| return $cache; | |
| } | |
| function lr_acf_suite_get( string $key ) { | |
| return lr_acf_suite_settings()[ $key ] ?? null; | |
| } | |
| /** | |
| * Parse the collapse_groups textarea into a normalised array of 'group_xxx' IDs. | |
| * Accepts both 'group_xxx' and 'acf-group_xxx' — strips the 'acf-' prefix if present. | |
| * FIX v1.1.1: validates each entry against the expected ACF key pattern before | |
| * accepting it, making the schema explicit rather than just stripping bad chars. | |
| * Result is statically cached for the request. | |
| */ | |
| function lr_acf_suite_collapse_ids(): array { | |
| static $ids = null; | |
| if ( $ids !== null ) return $ids; | |
| $raw = (string) lr_acf_suite_get( 'collapse_groups' ); | |
| $ids = []; | |
| foreach ( explode( "\n", $raw ) as $line ) { | |
| $id = trim( $line ); | |
| if ( $id === '' ) continue; | |
| if ( strpos( $id, 'acf-' ) === 0 ) { | |
| $id = substr( $id, 4 ); | |
| } | |
| // Only accept valid ACF group key format: group_[alphanumeric] | |
| if ( ! preg_match( '/^group_[a-z0-9]+$/i', $id ) ) continue; | |
| $ids[] = $id; | |
| } | |
| $ids = array_values( array_unique( $ids ) ); | |
| return $ids; | |
| } | |
| // ============================================================================= | |
| // 2 Performance | |
| // ============================================================================= | |
| // Attached at file load — before scripts are registered — intentionally outside | |
| // any hook. MU plugins load early enough that this is safe and correct. | |
| if ( lr_acf_suite_get( 'perf_disable_script_translations' ) ) { | |
| add_filter( 'load_script_translations', '__return_false' ); | |
| } | |
| add_action( 'plugins_loaded', function () { | |
| if ( ! function_exists( 'acf' ) ) return; | |
| $s = lr_acf_suite_settings(); | |
| if ( $s['perf_remove_autop'] ) | |
| add_filter( 'acf/settings/remove_wp_autop', '__return_true' ); | |
| if ( $s['perf_remove_autoformat'] ) | |
| add_filter( 'acf/settings/remove_wp_autoformat', '__return_true' ); | |
| if ( $s['perf_disable_media'] ) | |
| add_filter( 'acf/settings/enqueue_media', '__return_false' ); | |
| if ( $s['perf_disable_shortcodes'] ) | |
| add_filter( 'acf/settings/enable_shortcode_support', '__return_false' ); | |
| if ( $s['perf_acf_json'] ) { | |
| add_filter( 'acf/settings/save_json', function () { | |
| return get_stylesheet_directory() . '/acf-json'; | |
| } ); | |
| // Append to the existing path list rather than replacing it, so other | |
| // plugins or parent themes can still register their own JSON paths. | |
| // FIX v1.1.1: also guard against duplicates if the filter runs more | |
| // than once (e.g. theme + plugin both registering the same helper). | |
| add_filter( 'acf/settings/load_json', function ( array $paths ) { | |
| $path = get_stylesheet_directory() . '/acf-json'; | |
| if ( ! in_array( $path, $paths, true ) ) { | |
| $paths[] = $path; | |
| } | |
| return $paths; | |
| } ); | |
| } | |
| if ( $s['perf_disable_post_types'] ) | |
| add_filter( 'acf/settings/enable_post_types', '__return_false' ); | |
| if ( $s['perf_disable_options_ui'] ) | |
| add_filter( 'acf/settings/enable_options_pages_ui', '__return_false' ); | |
| if ( $s['perf_disable_field_tabs'] ) | |
| add_filter( 'acf/field_group/disable_field_settings_tabs', '__return_true' ); | |
| if ( $s['perf_disable_clone_templates'] ) | |
| add_filter( 'acf/settings/clone', '__return_false' ); | |
| } ); | |
| // ============================================================================= | |
| // 3 Collapse Groups | |
| // ============================================================================= | |
| add_action( 'current_screen', function ( WP_Screen $screen ) { | |
| if ( ! lr_acf_suite_get( 'collapse_enabled' ) ) return; | |
| if ( $screen->base !== 'post' && $screen->base !== 'post-new' ) return; | |
| $ids = lr_acf_suite_collapse_ids(); | |
| if ( empty( $ids ) ) return; | |
| // WordPress stores metabox open/closed state as 'acf-group_xxx' | |
| $metabox_ids = array_map( fn( $id ) => 'acf-' . $id, $ids ); | |
| add_filter( | |
| 'get_user_option_closedpostboxes_' . $screen->id, | |
| function ( $closed ) use ( $metabox_ids ) { | |
| if ( ! is_array( $closed ) ) $closed = []; | |
| foreach ( $metabox_ids as $box_id ) { | |
| if ( ! in_array( $box_id, $closed, true ) ) { | |
| $closed[] = $box_id; | |
| } | |
| } | |
| return $closed; | |
| } | |
| ); | |
| // Conditionally attach JS fallback only on relevant screens — avoids running | |
| // get_current_screen() inside the footer hook on every admin page. | |
| add_action( 'admin_print_footer_scripts', 'lr_acf_suite_collapse_js' ); | |
| } ); | |
| /** | |
| * JS fallback: re-collapses any group that another script may have re-opened | |
| * after DOMContentLoaded. Registered only on post/post-new screens (see above). | |
| */ | |
| function lr_acf_suite_collapse_js(): void { | |
| $ids = lr_acf_suite_collapse_ids(); | |
| if ( empty( $ids ) ) return; | |
| ?> | |
| <script> | |
| (function () { | |
| var ids = <?php echo wp_json_encode( $ids ); ?>; | |
| function collapseGroups() { | |
| ids.forEach( function ( id ) { | |
| var box = document.getElementById( 'acf-' + id ); | |
| if ( ! box ) return; | |
| box.classList.add( 'closed' ); | |
| var btn = box.querySelector( '.handlediv' ); | |
| if ( btn ) btn.setAttribute( 'aria-expanded', 'false' ); | |
| } ); | |
| } | |
| if ( document.readyState === 'loading' ) { | |
| document.addEventListener( 'DOMContentLoaded', collapseGroups ); | |
| } else { | |
| collapseGroups(); | |
| } | |
| }()); | |
| </script> | |
| <?php | |
| } | |
| // ============================================================================= | |
| // 4 Permissions | |
| // ============================================================================= | |
| add_action( 'init', function () { | |
| if ( ! lr_acf_suite_get( 'perms_caps_enabled' ) ) return; | |
| $role = get_role( 'administrator' ); | |
| if ( ! $role ) return; | |
| // Guard with has_cap() to avoid a DB write (update_option on wp_user_roles) | |
| // on every request when capabilities are already present. | |
| foreach ( [ 'acf_edit_fields', 'acf_edit_field_groups', 'acfe_admin_settings', 'acfe_admin_tools' ] as $cap ) { | |
| if ( ! $role->has_cap( $cap ) ) { | |
| $role->add_cap( $cap ); | |
| } | |
| } | |
| } ); | |
| add_filter( 'acf/location/rule_match/user_role', function ( $match, $rule, $screen ) { | |
| if ( ! lr_acf_suite_get( 'perms_user_role_fix' ) ) return $match; | |
| if ( ! is_user_logged_in() ) return false; | |
| $roles = (array) wp_get_current_user()->roles; | |
| return $rule['operator'] === '==' | |
| ? in_array( $rule['value'], $roles, true ) | |
| : ! in_array( $rule['value'], $roles, true ); | |
| }, 10, 3 ); | |
| // ============================================================================= | |
| // 5 HTML Escaping | |
| // ============================================================================= | |
| add_action( 'plugins_loaded', function () { | |
| if ( ! lr_acf_suite_get( 'escape_unsafe_html' ) ) return; | |
| add_filter( 'acf/shortcode/allow_unsafe_html', '__return_true' ); | |
| add_filter( 'acf/the_field/allow_unsafe_html', '__return_true' ); | |
| } ); | |
| // ============================================================================= | |
| // 6 Admin — menu + save handler | |
| // ============================================================================= | |
| add_action( 'admin_menu', function () { | |
| add_options_page( | |
| 'LR ACF Suite', | |
| 'LR ACF Suite', | |
| 'manage_options', | |
| 'lr-acf-suite', | |
| 'lr_acf_suite_render_page' | |
| ); | |
| } ); | |
| add_action( 'admin_init', function () { | |
| if ( ! isset( $_POST['lr_acf_suite_submit'] ) ) return; | |
| if ( ! check_admin_referer( LR_ACF_SUITE_NONCE ) ) wp_die( 'Security check failed.' ); | |
| if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Insufficient permissions.' ); | |
| $bool_keys = [ | |
| 'perf_remove_autop', 'perf_remove_autoformat', 'perf_disable_media', | |
| 'perf_disable_shortcodes', 'perf_acf_json', 'perf_disable_post_types', | |
| 'perf_disable_options_ui', 'perf_disable_field_tabs', | |
| 'perf_disable_script_translations', 'perf_disable_clone_templates', | |
| 'collapse_enabled', 'perms_caps_enabled', 'perms_user_role_fix', | |
| 'escape_unsafe_html', | |
| ]; | |
| $new = []; | |
| foreach ( $bool_keys as $key ) { | |
| $new[ $key ] = isset( $_POST[ $key ] ) ? 1 : 0; | |
| } | |
| // Strip characters outside the ACF group key alphabet before storing. | |
| // The pattern-level validation in lr_acf_suite_collapse_ids() is the real | |
| // gate; this just keeps the stored value clean. | |
| $raw_groups = wp_unslash( $_POST['collapse_groups'] ?? '' ); | |
| $new['collapse_groups'] = preg_replace( '/[^a-zA-Z0-9_\n\-]/', '', $raw_groups ); | |
| update_option( LR_ACF_SUITE_OPTION, $new ); | |
| wp_safe_redirect( admin_url( 'options-general.php?page=lr-acf-suite&saved=1' ) ); | |
| exit; | |
| } ); | |
| // ============================================================================= | |
| // 7 Admin — styles (only injected on the plugin page) | |
| // ============================================================================= | |
| add_action( 'admin_head', function () { | |
| $screen = get_current_screen(); | |
| if ( ! $screen || $screen->id !== 'settings_page_lr-acf-suite' ) return; | |
| echo '<style>' . lr_acf_suite_css() . '</style>'; | |
| } ); | |
| function lr_acf_suite_css(): string { return ' | |
| .lr-suite{max-width:860px} | |
| .lr-suite h1{display:flex;align-items:center;gap:10px;font-size:1.4rem;margin-bottom:4px} | |
| .lr-suite__version{font-size:11px;background:#e8f0fe;color:#2271b1;padding:2px 9px;border-radius:99px;font-weight:600;letter-spacing:.02em} | |
| .lr-suite__subtitle{color:#646970;margin-top:0;margin-bottom:28px} | |
| .lr-suite__card{background:#fff;border:1px solid #dcdcde;border-radius:6px;margin-bottom:20px;overflow:hidden} | |
| .lr-suite__card-head{display:flex;align-items:center;gap:14px;padding:16px 22px;border-bottom:1px solid #f0f0f1} | |
| .lr-suite__card-icon{width:34px;height:34px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0} | |
| .lr-suite__card-icon svg{width:18px;height:18px} | |
| .lr-suite__card-title{font-size:.9375rem;font-weight:600;margin:0;color:#1d2327;line-height:1.3} | |
| .lr-suite__card-desc{font-size:12px;color:#646970;margin:3px 0 0;line-height:1.4} | |
| .lr-suite__card-body{padding:4px 0} | |
| .lr-suite__card--perf .lr-suite__card-icon{background:#e8f0fe;color:#2271b1} | |
| .lr-suite__card--collapse .lr-suite__card-icon{background:#f0ebff;color:#7c3aed} | |
| .lr-suite__card--perms .lr-suite__card-icon{background:#e8faf1;color:#0d7a4e} | |
| .lr-suite__card--escape .lr-suite__card-icon{background:#fff3cd;color:#9a6700} | |
| .lr-row{display:flex;align-items:flex-start;gap:16px;padding:11px 22px;border-bottom:1px solid #f6f7f7} | |
| .lr-row:last-child{border-bottom:0} | |
| .lr-row__label{flex:1;min-width:0} | |
| .lr-row__name{font-size:13px;font-weight:500;color:#1d2327;display:block;line-height:1.4} | |
| .lr-row__hint{font-size:11.5px;color:#646970;display:block;margin-top:2px;line-height:1.5} | |
| .lr-row__hint code,.lr-groups-hint code,.lr-warning code{font-size:11px;background:#f6f7f7;padding:1px 5px;border-radius:3px;color:#50575e} | |
| .lr-toggle{position:relative;display:inline-flex;align-items:center;cursor:pointer;flex-shrink:0;margin-top:2px} | |
| .lr-toggle input{position:absolute;opacity:0;width:0;height:0;pointer-events:none} | |
| .lr-toggle__track{width:38px;height:20px;background:#c3c4c7;border-radius:10px;position:relative;transition:background .15s ease} | |
| .lr-toggle__track::after{content:"";position:absolute;top:3px;left:3px;width:14px;height:14px;background:#fff;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.25);transition:transform .15s ease} | |
| .lr-toggle input:checked + .lr-toggle__track{background:#2271b1} | |
| .lr-toggle input:checked + .lr-toggle__track::after{transform:translateX(18px)} | |
| .lr-toggle input:focus-visible + .lr-toggle__track{outline:2px solid #2271b1;outline-offset:2px} | |
| .lr-toggle--danger input:checked + .lr-toggle__track{background:#b32d2e} | |
| .lr-groups-area{padding:14px 22px 18px;border-bottom:1px solid #f6f7f7} | |
| .lr-groups-area label{display:block;font-size:11px;font-weight:700;color:#50575e;margin-bottom:6px;text-transform:uppercase;letter-spacing:.05em} | |
| .lr-groups-area textarea{width:100%;max-width:420px;font-family:monospace;font-size:12.5px;border:1px solid #dcdcde;border-radius:4px;padding:8px 10px;resize:vertical;color:#1d2327;line-height:1.75} | |
| .lr-groups-area textarea:focus{border-color:#2271b1;outline:2px solid #2271b1;outline-offset:0;box-shadow:none} | |
| .lr-groups-hint{font-size:11.5px;color:#646970;margin-top:7px;line-height:1.5} | |
| .lr-warning{display:flex;gap:10px;background:#fff8e5;border:1px solid #f0c33c;border-radius:4px;padding:10px 14px;margin:14px 22px 4px;font-size:12px;color:#7a5c00;line-height:1.5} | |
| .lr-warning svg{flex-shrink:0;width:15px;height:15px;margin-top:1px} | |
| .lr-suite__footer{display:flex;align-items:center;gap:14px;padding:6px 0 28px} | |
| .lr-suite__footer .button-primary{padding:0 20px;height:36px;font-size:13px} | |
| .lr-saved{font-size:12.5px;color:#0d7a4e;font-weight:500} | |
| .lr-saved::before{content:"✓ "} | |
| '; } | |
| // ============================================================================= | |
| // 8 Admin — page render | |
| // ============================================================================= | |
| function lr_acf_suite_render_page(): void { | |
| if ( ! current_user_can( 'manage_options' ) ) return; | |
| $s = lr_acf_suite_settings(); | |
| $saved = isset( $_GET['saved'] ); | |
| $icon = [ | |
| 'perf' => '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clip-rule="evenodd"/></svg>', | |
| 'collapse'=> '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" clip-rule="evenodd"/></svg>', | |
| 'perms' => '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 2a5 5 0 00-5 5v1H4a2 2 0 00-2 2v6a2 2 0 002 2h12a2 2 0 002-2v-6a2 2 0 00-2-2h-1V7a5 5 0 00-5-5zm3 6V7a3 3 0 10-6 0v1h6zm-3 3a1 1 0 011 1v2a1 1 0 11-2 0v-2a1 1 0 011-1z" clip-rule="evenodd"/></svg>', | |
| 'escape' => '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>', | |
| 'warn' => '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>', | |
| ]; | |
| $allowed_hint = [ 'code' => [] ]; | |
| ?> | |
| <div class="wrap lr-suite"> | |
| <h1> | |
| LR ACF Suite | |
| <span class="lr-suite__version">v<?php echo esc_html( LR_ACF_SUITE_VER ); ?></span> | |
| </h1> | |
| <p class="lr-suite__subtitle">Unified ACF/ACFE configuration — Less Rain projects.</p> | |
| <?php if ( $saved ): ?> | |
| <div class="notice notice-success is-dismissible"><p>Settings saved.</p></div> | |
| <?php endif; ?> | |
| <form method="post"> | |
| <?php wp_nonce_field( LR_ACF_SUITE_NONCE ); ?> | |
| <?php | |
| $row = function ( string $key, string $name, string $hint ) use ( $s, $allowed_hint ): void { | |
| printf( | |
| '<div class="lr-row"> | |
| <div class="lr-row__label"> | |
| <span class="lr-row__name">%s</span> | |
| <span class="lr-row__hint">%s</span> | |
| </div> | |
| <label class="lr-toggle"> | |
| <input type="checkbox" name="%s" value="1"%s> | |
| <span class="lr-toggle__track"></span> | |
| </label> | |
| </div>', | |
| esc_html( $name ), | |
| wp_kses( $hint, $allowed_hint ), | |
| esc_attr( $key ), | |
| checked( $s[ $key ] ?? 0, 1, false ) | |
| ); | |
| }; | |
| ?> | |
| <!-- ── PERFORMANCE ─────────────────────────────────────────── --> | |
| <div class="lr-suite__card lr-suite__card--perf"> | |
| <div class="lr-suite__card-head"> | |
| <div class="lr-suite__card-icon"><?php echo $icon['perf']; ?></div> | |
| <div> | |
| <p class="lr-suite__card-title">Performance</p> | |
| <p class="lr-suite__card-desc">Reduce ACF admin overhead in large Flexible Content setups. All settings are admin-only — no front-end impact.</p> | |
| </div> | |
| </div> | |
| <div class="lr-suite__card-body"> | |
| <?php | |
| $row( 'perf_remove_autop', 'Remove wpautop', 'Strips WordPress automatic paragraph formatting from the WYSIWYG editor.' ); | |
| $row( 'perf_remove_autoformat', 'Remove autoformat', 'Disables ACF\'s automatic text formatting on field values.' ); | |
| $row( 'perf_disable_media', 'Disable media library enqueue', 'Skips loading WP media scripts on edit screens. Speeds up editor load when no image/file fields are visible.' ); | |
| $row( 'perf_disable_shortcodes', 'Disable shortcode support', 'Turns off ACF\'s built-in shortcode parser.' ); | |
| $row( 'perf_acf_json', 'Enable ACF JSON sync', 'Appends <code>theme/acf-json/</code> to ACF\'s JSON load paths and sets it as the save target. Other registered paths (plugins, child themes) are preserved. Duplicate paths are deduplicated automatically.' ); | |
| $row( 'perf_disable_post_types', 'Disable Post Types UI', 'Hides the ACF Post Types registration panel from the admin menu.' ); | |
| $row( 'perf_disable_options_ui', 'Disable Options Pages UI', 'Hides the ACF Options Pages registration panel from the admin menu.' ); | |
| $row( 'perf_disable_field_tabs', 'Disable field settings tabs', 'Removes tab navigation inside the field settings panel, reducing DOM weight in large field groups.' ); | |
| $row( 'perf_disable_script_translations','Disable script translations', 'Prevents WordPress loading <code>.json</code> translation files for scripts. Reduces HTTP requests in multilingual setups.' ); | |
| $row( 'perf_disable_clone_templates', 'Remove hidden clone templates', 'Stops ACF outputting hidden clone HTML templates — reduces DOM weight when using the Clone field.' ); | |
| ?> | |
| </div> | |
| </div> | |
| <!-- ── COLLAPSE GROUPS ─────────────────────────────────────── --> | |
| <div class="lr-suite__card lr-suite__card--collapse"> | |
| <div class="lr-suite__card-head"> | |
| <div class="lr-suite__card-icon"><?php echo $icon['collapse']; ?></div> | |
| <div> | |
| <p class="lr-suite__card-title">Collapse Field Groups</p> | |
| <p class="lr-suite__card-desc">Force specific ACF field groups to load in a collapsed state — instantly, without flicker.</p> | |
| </div> | |
| </div> | |
| <div class="lr-suite__card-body"> | |
| <?php $row( 'collapse_enabled', 'Enable collapse', 'Activates the PHP user-option override (no flicker) plus a JS fallback for all group IDs listed below.' ); ?> | |
| <div class="lr-groups-area"> | |
| <label for="lr-collapse-groups">Field Group IDs — one per line</label> | |
| <textarea id="lr-collapse-groups" name="collapse_groups" rows="7" spellcheck="false"><?php echo esc_textarea( $s['collapse_groups'] ?? '' ); ?></textarea> | |
| <p class="lr-groups-hint"> | |
| Use the ACF group key, e.g. <code>group_645e45603884f</code>. The <code>acf-</code> prefix is accepted and stripped automatically. Lines not matching <code>group_[alphanumeric]</code> are silently ignored.<br> | |
| Find the key in the URL when editing a field group (<code>?post=NNN</code>), or inside your <code>acf-json/*.json</code> files under the <code>"key"</code> property. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ── PERMISSIONS ─────────────────────────────────────────── --> | |
| <div class="lr-suite__card lr-suite__card--perms"> | |
| <div class="lr-suite__card-head"> | |
| <div class="lr-suite__card-icon"><?php echo $icon['perms']; ?></div> | |
| <div> | |
| <p class="lr-suite__card-title">Permissions</p> | |
| <p class="lr-suite__card-desc">Missing ACF/ACFE capability fixes for the administrator role and location rules.</p> | |
| </div> | |
| </div> | |
| <div class="lr-suite__card-body"> | |
| <?php | |
| $row( | |
| 'perms_caps_enabled', | |
| 'Add ACF/ACFE capabilities to Administrator', | |
| 'Grants <code>acf_edit_fields</code>, <code>acf_edit_field_groups</code>, <code>acfe_admin_settings</code> and <code>acfe_admin_tools</code> — only writes to the DB if a capability is not already present. Disabling stops re-adding them but does not revoke existing capabilities.' | |
| ); | |
| $row( | |
| 'perms_user_role_fix', | |
| 'Fix user_role location rules globally', | |
| 'Extends ACF\'s location rule matching so <code>user_role</code> conditions evaluate correctly on all admin screens, not only the user profile screen.' | |
| ); | |
| ?> | |
| </div> | |
| </div> | |
| <!-- ── HTML ESCAPING ───────────────────────────────────────── --> | |
| <div class="lr-suite__card lr-suite__card--escape"> | |
| <div class="lr-suite__card-head"> | |
| <div class="lr-suite__card-icon"><?php echo $icon['escape']; ?></div> | |
| <div> | |
| <p class="lr-suite__card-title">HTML Escaping</p> | |
| <p class="lr-suite__card-desc">Control ACF's security escaping for field output.</p> | |
| </div> | |
| </div> | |
| <div class="lr-suite__card-body"> | |
| <div class="lr-warning"> | |
| <?php echo $icon['warn']; ?> | |
| <span>Enabling this allows raw HTML — including <code><script></code> tags — to be output by <code>the_field()</code> and ACF shortcodes. Only enable if all field content is entered by trusted editors.</span> | |
| </div> | |
| <div class="lr-row"> | |
| <div class="lr-row__label"> | |
| <span class="lr-row__name">Allow unsafe HTML in ACF output</span> | |
| <span class="lr-row__hint">Bypasses ACF's <code>esc_html()</code> sanitization via <code>acf/the_field/allow_unsafe_html</code> and <code>acf/shortcode/allow_unsafe_html</code>. Off by default.</span> | |
| </div> | |
| <label class="lr-toggle lr-toggle--danger"> | |
| <input type="checkbox" name="escape_unsafe_html" value="1" <?php checked( $s['escape_unsafe_html'] ?? 0, 1 ); ?>> | |
| <span class="lr-toggle__track"></span> | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ── FOOTER ──────────────────────────────────────────────── --> | |
| <div class="lr-suite__footer"> | |
| <button type="submit" name="lr_acf_suite_submit" class="button button-primary button-large">Save Settings</button> | |
| <?php if ( $saved ): ?> | |
| <span class="lr-saved">Settings saved</span> | |
| <?php endif; ?> | |
| </div> | |
| </form> | |
| </div> | |
| <?php | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment