Skip to content

Instantly share code, notes, and snippets.

@flyingwebie
Created August 14, 2026 14:33
Show Gist options
  • Select an option

  • Save flyingwebie/d9c6a9d390dc380ee142bdb77ba1a550 to your computer and use it in GitHub Desktop.

Select an option

Save flyingwebie/d9c6a9d390dc380ee142bdb77ba1a550 to your computer and use it in GitHub Desktop.
Menu Manager for ANY Wordpress Website
<?php
/**
* FWS — Admin Menu Manager (Groups + Hide, UI Edition)
* ------------------------------------------------------------------
* One WPCodeBox2 snippet that does three things from a single screen
* (Tools → Menu Manager):
*
* 1. GROUP the left sidebar items under custom headers
* (Management, eCommerce, Administration...) with a separator
* line above each group. Drag to order items and whole groups.
*
* 2. HIDE chosen top-level items (and specific submenus) from
* everyone EXCEPT the privileged users you list.
*
* 3. PREVIEW: privileged users get an admin-bar toggle to switch
* the hidden state on/off so they can see what normal users see.
*
* SETUP:
* - Paste in ONE PHP snippet. Context: Admin only (Backend).
* - Go to Tools → Menu Manager.
* - Put your super-admin username(s) in "Privileged users".
* - Drag items into groups, or into the "Hidden" column.
* - Save. Use the "Admin Hide" button in the top bar to preview.
*
* NOTE: if you hide Tools for normal users, privileged users can still
* reach this page (toggle preview off, or open it directly).
* ------------------------------------------------------------------
*/
if ( ! defined( 'ABSPATH' ) ) { exit; }
class FWS_Menu_Manager {
const OPTION = 'fws_menu_manager_config';
const STATE = 'fws_menu_hide_active'; // preview toggle state
const SLUG = 'fws-menu-manager';
const NONCE = 'fws_menu_toggle';
private $all_items = array(); // snapshot of every top-level item (slug => label)
public function __construct() {
add_action( 'admin_menu', array( $this, 'snapshot_menu' ), 9990 ); // before reorder
add_action( 'admin_menu', array( $this, 'register_page' ) );
add_action( 'admin_menu', array( $this, 'apply_menu' ), 9999 ); // hide + reorder
add_action( 'admin_head', array( $this, 'group_styles' ) );
add_action( 'admin_bar_menu', array( $this, 'toggle_button' ), 999 );
add_action( 'admin_footer', array( $this, 'toggle_script' ) );
add_action( 'wp_ajax_fws_toggle_hide', array( $this, 'ajax_toggle' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}
/* ================================================================
* CONFIG
* ================================================================*/
private function defaults() {
return array(
'privileged' => array( 'superadmin' ),
'unassigned_label' => 'Other',
'groups' => array(
array( 'label' => 'Content', 'items' => array(
'index.php', 'edit.php?post_type=page', 'upload.php', 'edit-comments.php',
) ),
),
'hidden' => array(
'themes.php', 'plugins.php', 'tools.php', 'edit.php', 'users.php',
'options-general.php', 'automatic-css', 'rank-math', 'meta-box',
'wp-mail-smtp', 'mepr-toolbox', 'oxy_user_library', 'litespeed', 'etch',
),
'hidden_sub' => array( 'index.php' => array( 'update-core.php' ) ),
);
}
private function get_config() {
$cfg = get_option( self::OPTION );
if ( ! is_array( $cfg ) ) { $cfg = $this->defaults(); }
$cfg = wp_parse_args( $cfg, array(
'privileged' => array(), 'unassigned_label' => 'Other',
'groups' => array(), 'hidden' => array(), 'hidden_sub' => array(),
) );
return $cfg;
}
private function is_privileged() {
$cfg = $this->get_config();
$user = wp_get_current_user();
return in_array( $user->user_login, (array) $cfg['privileged'], true );
}
/** Should hidden items actually be hidden for the CURRENT user right now? */
private function should_hide() {
if ( ! $this->is_privileged() ) { return true; } // normal users: always hide
return (bool) get_option( self::STATE, false ); // privileged: only when preview ON
}
/* ================================================================
* MENU: snapshot, hide + reorder
* ================================================================*/
public function snapshot_menu() {
global $menu;
if ( empty( $menu ) ) { return; }
foreach ( $menu as $item ) {
if ( ! isset( $item[2], $item[0] ) ) { continue; }
if ( isset( $item[4] ) && false !== strpos( (string) $item[4], 'wp-menu-separator' ) ) { continue; }
if ( 0 === strpos( $item[2], 'fws-group-' ) ) { continue; }
$label = $this->clean_label( $item[0] );
$this->all_items[ $item[2] ] = ( $label !== '' ) ? $label : $item[2];
}
}
public function apply_menu() {
global $menu;
if ( empty( $menu ) || ! is_array( $menu ) ) { return; }
$cfg = $this->get_config();
$hide = $this->should_hide();
// 1) Hide submenus (only when hiding is active).
if ( $hide && ! empty( $cfg['hidden_sub'] ) ) {
foreach ( $cfg['hidden_sub'] as $parent => $children ) {
foreach ( (array) $children as $child ) {
remove_submenu_page( $parent, $child );
}
}
}
// Build the set of top-level slugs to remove: the Hidden column +
// every item inside a group flagged "hidden". Only when hiding is active.
$hidden_top = array();
if ( $hide ) {
foreach ( (array) $cfg['hidden'] as $s ) { $hidden_top[ $s ] = true; }
foreach ( $cfg['groups'] as $g ) {
if ( ! empty( $g['hidden'] ) ) {
foreach ( (array) ( isset( $g['items'] ) ? $g['items'] : array() ) as $s ) {
$hidden_top[ $s ] = true;
}
}
}
}
// 2) Index real items (skip separators, our headers, and hidden ones).
$by_slug = array();
foreach ( $menu as $item ) {
if ( ! isset( $item[2] ) ) { continue; }
if ( isset( $item[4] ) && false !== strpos( (string) $item[4], 'wp-menu-separator' ) ) { continue; }
if ( 0 === strpos( $item[2], 'fws-group-' ) ) { continue; }
if ( isset( $hidden_top[ $item[2] ] ) ) { continue; }
$by_slug[ $item[2] ] = $item;
}
// 3) Rebuild with group headers.
$used = array(); $new_menu = array(); $pos = 1;
$add_header = function( $label ) use ( &$new_menu, &$pos ) {
if ( $label === '' || $label === false || $label === null ) { return; }
$slug = 'fws-group-' . sanitize_title( $label );
$new_menu[ $pos++ ] = array(
esc_html( $label ), 'read', $slug, '',
'wp-not-current-submenu menu-top fws-menu-group', $slug, '',
);
};
foreach ( $cfg['groups'] as $group ) {
$label = isset( $group['label'] ) ? $group['label'] : '';
$items = array();
foreach ( (array) ( isset( $group['items'] ) ? $group['items'] : array() ) as $slug ) {
if ( isset( $by_slug[ $slug ] ) && ! isset( $used[ $slug ] ) ) {
$items[] = $by_slug[ $slug ]; $used[ $slug ] = true;
}
}
if ( empty( $items ) ) { continue; }
$add_header( $label );
foreach ( $items as $gi ) { $new_menu[ $pos++ ] = $gi; }
}
// 4) Leftovers.
if ( $cfg['unassigned_label'] !== false ) {
$leftovers = array();
foreach ( $by_slug as $slug => $item ) {
if ( ! isset( $used[ $slug ] ) ) { $leftovers[] = $item; }
}
if ( ! empty( $leftovers ) ) {
$add_header( $cfg['unassigned_label'] );
foreach ( $leftovers as $li ) { $new_menu[ $pos++ ] = $li; }
}
}
$menu = $new_menu;
}
/* ================================================================
* ADMIN-BAR PREVIEW TOGGLE (privileged users only)
* ================================================================*/
public function toggle_button( $admin_bar ) {
if ( ! $this->is_privileged() ) { return; }
$active = (bool) get_option( self::STATE, false );
$admin_bar->add_menu( array(
'id' => 'fws-hide-toggle',
'title' => '<span class="ab-icon"></span>' . ( $active ? 'Disable Admin Hide' : 'Enable Admin Hide' ),
'href' => '#',
'meta' => array( 'class' => $active ? 'fws-hide-active' : 'fws-hide-inactive' ),
) );
}
public function toggle_script() {
if ( ! $this->is_privileged() ) { return; }
$nonce = wp_create_nonce( self::NONCE );
?>
<script type="text/javascript">
jQuery(function($){
$('#wp-admin-bar-fws-hide-toggle').on('click', function(e){
e.preventDefault();
$.post(ajaxurl, { action:'fws_toggle_hide', _nonce:'<?php echo esc_js( $nonce ); ?>' }, function(r){
if ( r && r.success ) { location.reload(); }
});
});
});
</script>
<style>
#wp-admin-bar-fws-hide-toggle .ab-icon:before { content:"\f160"; top:2px; }
#wp-admin-bar-fws-hide-toggle.fws-hide-active .ab-icon:before { color:#dc3232; }
#wp-admin-bar-fws-hide-toggle.fws-hide-inactive .ab-icon:before { color:#46b450; }
</style>
<?php
}
public function ajax_toggle() {
if ( ! $this->is_privileged() ) { wp_send_json_error( 'Insufficient permissions' ); }
check_ajax_referer( self::NONCE, '_nonce' );
$new = ! (bool) get_option( self::STATE, false );
update_option( self::STATE, $new );
wp_send_json_success( array( 'is_active' => $new ) );
}
/* ================================================================
* SETTINGS PAGE
* ================================================================*/
public function register_page() {
add_management_page( 'Menu Manager', 'Menu Manager', 'manage_options', self::SLUG, array( $this, 'render_page' ) );
}
public function enqueue_assets( $hook ) {
// Tools → Menu Manager. Hook suffix for a management page is "tools_page_{slug}".
if ( 'tools_page_' . self::SLUG !== $hook ) { return; }
wp_enqueue_script( 'jquery-ui-core' );
wp_enqueue_script( 'jquery-ui-mouse' );
wp_enqueue_script( 'jquery-ui-sortable' );
}
private function clean_label( $raw ) {
$raw = preg_replace( '/<[^>]*>/', '', (string) $raw );
$raw = preg_replace( '/\s+\d+$/', '', trim( $raw ) );
return trim( $raw );
}
private function maybe_save() {
if ( empty( $_POST['fws_save'] ) ) { return false; }
if ( ! current_user_can( 'manage_options' ) ) { return false; }
check_admin_referer( 'fws_menu_manager_save' );
$cfg = $this->get_config();
// groups + hidden come as JSON from the drag UI
$data = json_decode( wp_unslash( $_POST['fws_config_json'] ), true );
$cfg['groups'] = array();
$cfg['hidden'] = array();
if ( is_array( $data ) ) {
if ( ! empty( $data['groups'] ) ) {
foreach ( $data['groups'] as $g ) {
$label = isset( $g['label'] ) ? sanitize_text_field( $g['label'] ) : '';
$items = array();
foreach ( (array) ( isset( $g['items'] ) ? $g['items'] : array() ) as $s ) {
$items[] = sanitize_text_field( $s );
}
if ( $label === '' && empty( $items ) ) { continue; }
$cfg['groups'][] = array(
'label' => $label,
'items' => $items,
'hidden' => ! empty( $g['hidden'] ),
);
}
}
if ( ! empty( $data['hidden'] ) ) {
foreach ( (array) $data['hidden'] as $s ) { $cfg['hidden'][] = sanitize_text_field( $s ); }
}
}
// privileged users (comma/space/newline separated)
$raw_users = isset( $_POST['fws_privileged'] ) ? wp_unslash( $_POST['fws_privileged'] ) : '';
$users = preg_split( '/[\s,]+/', $raw_users, -1, PREG_SPLIT_NO_EMPTY );
$cfg['privileged'] = array_map( 'sanitize_user', $users );
// unassigned label
$cfg['unassigned_label'] = sanitize_text_field( wp_unslash( $_POST['fws_unassigned_label'] ?? 'Other' ) );
// hidden submenus: lines of "parent | child"
$cfg['hidden_sub'] = array();
$lines = preg_split( '/\r\n|\r|\n/', wp_unslash( $_POST['fws_hidden_sub'] ?? '' ) );
foreach ( $lines as $line ) {
if ( strpos( $line, '|' ) === false ) { continue; }
list( $parent, $child ) = array_map( 'trim', explode( '|', $line, 2 ) );
if ( $parent === '' || $child === '' ) { continue; }
$cfg['hidden_sub'][ $parent ][] = $child;
}
update_option( self::OPTION, $cfg );
return true;
}
public function render_page() {
$saved = $this->maybe_save();
$cfg = $this->get_config();
$live = $this->all_items; // full snapshot (includes items hidden in preview)
// placement maps
$in_group = array();
foreach ( $cfg['groups'] as $g ) {
foreach ( (array) $g['items'] as $s ) { $in_group[ $s ] = true; }
}
$is_hidden = array_flip( (array) $cfg['hidden'] );
// unassigned = live items not grouped and not hidden
$unassigned = array();
foreach ( $live as $slug => $label ) {
if ( ! isset( $in_group[ $slug ] ) && ! isset( $is_hidden[ $slug ] ) ) {
$unassigned[ $slug ] = $label;
}
}
$item_html = function( $slug ) use ( $live ) {
$label = isset( $live[ $slug ] ) ? $live[ $slug ] : $slug;
printf(
'<li class="fws-item" data-slug="%s"><span class="dashicons dashicons-menu-alt2"></span> %s <code>%s</code></li>',
esc_attr( $slug ), esc_html( $label ), esc_html( $slug )
);
};
// hidden_sub -> textarea
$sub_lines = array();
foreach ( (array) $cfg['hidden_sub'] as $parent => $children ) {
foreach ( (array) $children as $child ) { $sub_lines[] = $parent . ' | ' . $child; }
}
?>
<div class="wrap fws-mm">
<h1>Menu Manager</h1>
<?php if ( $saved ) : ?>
<div class="notice notice-success is-dismissible"><p>Saved. Reload any admin page to see changes.</p></div>
<?php endif; ?>
<form method="post" id="fws-mm-form">
<?php wp_nonce_field( 'fws_menu_manager_save' ); ?>
<input type="hidden" name="fws_save" value="1">
<input type="hidden" name="fws_config_json" id="fws_config_json" value="">
<table class="form-table">
<tr>
<th><label for="fws_privileged">Privileged users</label></th>
<td>
<input type="text" class="regular-text" id="fws_privileged" name="fws_privileged"
value="<?php echo esc_attr( implode( ', ', (array) $cfg['privileged'] ) ); ?>"
placeholder="superadmin, anotheradmin">
<p class="description">Usernames (comma separated) who see everything and get the top-bar preview toggle. Everyone else sees the hidden items removed.</p>
</td>
</tr>
<tr>
<th><label for="fws_unassigned_label">Leftover items header</label></th>
<td>
<input type="text" id="fws_unassigned_label" name="fws_unassigned_label"
value="<?php echo esc_attr( $cfg['unassigned_label'] ); ?>" placeholder="Other">
<p class="description">Header for visible items not placed in any group. Leave blank for no header.</p>
</td>
</tr>
</table>
<p>
<button type="button" class="button button-secondary" id="fws-add-group">+ Add group</button>
<button type="submit" class="button button-primary">Save Changes</button>
</p>
<p class="description">Drag items between columns. Drag a column by its <span class="dashicons dashicons-move"></span> handle to reorder groups. Items in the <strong>Hidden</strong> column are removed for non-privileged users. Click a group's <span class="dashicons dashicons-visibility"></span> icon to hide that whole group from non-privileged users.</p>
<div id="fws-board">
<?php foreach ( $cfg['groups'] as $g ) :
$g_hidden = ! empty( $g['hidden'] );
$eye = $g_hidden ? 'dashicons-hidden' : 'dashicons-visibility';
?>
<div class="fws-group-box<?php echo $g_hidden ? ' fws-group-hidden' : ''; ?>">
<div class="fws-group-head">
<span class="fws-group-move dashicons dashicons-move" title="Drag to reorder group"></span>
<input type="text" class="fws-group-name" value="<?php echo esc_attr( $g['label'] ); ?>" placeholder="Group name">
<button type="button" class="button-link fws-hide-group" title="Hide this group from normal users"><span class="dashicons <?php echo esc_attr( $eye ); ?>"></span></button>
<button type="button" class="button-link fws-del-group" title="Delete group">&times;</button>
</div>
<ul class="fws-sortable">
<?php foreach ( (array) $g['items'] as $slug ) { if ( isset( $live[ $slug ] ) || true ) { $item_html( $slug ); } } ?>
</ul>
</div>
<?php endforeach; ?>
<div class="fws-group-box fws-hidden-box">
<div class="fws-group-head"><span class="dashicons dashicons-hidden"></span> <strong>Hidden</strong></div>
<ul class="fws-sortable" id="fws-hidden">
<?php foreach ( (array) $cfg['hidden'] as $slug ) { $item_html( $slug ); } ?>
</ul>
</div>
<div class="fws-group-box fws-unassigned-box">
<div class="fws-group-head"><strong>Unassigned</strong></div>
<ul class="fws-sortable" id="fws-unassigned">
<?php foreach ( $unassigned as $slug => $label ) { $item_html( $slug ); } ?>
</ul>
</div>
</div>
<h2 style="margin-top:24px;">Hide submenus (advanced)</h2>
<p class="description">One per line as <code>parent-slug | submenu-slug</code>, e.g. <code>index.php | update-core.php</code></p>
<textarea name="fws_hidden_sub" rows="4" class="large-text code" placeholder="index.php | update-core.php"><?php echo esc_textarea( implode( "\n", $sub_lines ) ); ?></textarea>
<p><button type="submit" class="button button-primary">Save Changes</button></p>
</form>
<script type="text/template" id="fws-group-tpl">
<div class="fws-group-box">
<div class="fws-group-head">
<span class="fws-group-move dashicons dashicons-move" title="Drag to reorder group"></span>
<input type="text" class="fws-group-name" value="" placeholder="Group name">
<button type="button" class="button-link fws-hide-group" title="Hide this group from normal users"><span class="dashicons dashicons-visibility"></span></button>
<button type="button" class="button-link fws-del-group" title="Delete group">&times;</button>
</div>
<ul class="fws-sortable"></ul>
</div>
</script>
</div>
<style>
.fws-mm #fws-board { display:flex; flex-wrap:wrap; gap:16px; align-items:flex-start; margin-top:10px; }
.fws-mm .fws-group-box { background:#fff; border:1px solid #c3c4c7; border-radius:6px; width:250px; }
.fws-mm .fws-group-head { display:flex; align-items:center; gap:6px; padding:8px; border-bottom:1px solid #dcdcde; background:#f6f7f7; border-radius:6px 6px 0 0; }
.fws-mm .fws-group-move { cursor:move; color:#888; }
.fws-mm .fws-group-name { width:100%; font-weight:600; }
.fws-mm .fws-hide-group { color:#646970; text-decoration:none; line-height:1; }
.fws-mm .fws-hide-group:hover { color:#2271b1; }
.fws-mm .fws-del-group { color:#b32d2e; font-size:18px; text-decoration:none; line-height:1; }
/* a group flagged hidden */
.fws-mm .fws-group-hidden { border-color:#e6b8b8; }
.fws-mm .fws-group-hidden .fws-group-head { background:#fcf3f3; }
.fws-mm .fws-group-hidden .fws-hide-group { color:#dc3232; }
.fws-mm .fws-group-hidden .fws-item { background:#fbeaea; border-color:#e6b8b8; }
.fws-mm .fws-sortable { min-height:48px; margin:0; padding:8px; list-style:none; }
.fws-mm .fws-item { background:#f0f6fc; border:1px solid #c5d9ed; border-radius:4px; padding:6px 8px; margin:0 0 6px; cursor:move; font-size:13px; }
.fws-mm .fws-item code { opacity:.6; font-size:11px; }
.fws-mm .fws-item .dashicons { font-size:16px; vertical-align:text-bottom; color:#888; }
.fws-mm .fws-hidden-box { background:#fcf3f3; border-color:#e6b8b8; }
.fws-mm .fws-hidden-box .fws-item { background:#fbeaea; border-color:#e6b8b8; }
.fws-mm .fws-unassigned-box { background:#fbfbfc; }
.fws-mm .fws-placeholder { height:32px; border:1px dashed #2271b1; border-radius:4px; margin:0 0 6px; background:#f0f6fc; }
.fws-mm .fws-col-placeholder { width:250px; border:2px dashed #2271b1; border-radius:6px; background:#f0f6fc; }
</style>
<script>
jQuery(function($){
function initSortables(){
$('.fws-sortable').sortable({
connectWith:'.fws-sortable', placeholder:'fws-placeholder', forcePlaceholderSize:true
}).disableSelection();
}
initSortables();
$('#fws-board').sortable({
handle:'.fws-group-move',
items:'> .fws-group-box:not(.fws-unassigned-box):not(.fws-hidden-box)',
placeholder:'fws-col-placeholder', forcePlaceholderSize:true, cancel:'input,.fws-sortable',
stop:function(){ $('#fws-board').append($('.fws-hidden-box')).append($('.fws-unassigned-box')); }
});
$('#fws-add-group').on('click', function(){
$('#fws-board').prepend($('#fws-group-tpl').html());
initSortables();
$('#fws-board .fws-group-box').first().find('.fws-group-name').focus();
});
$('#fws-board').on('click', '.fws-del-group', function(){
var $box = $(this).closest('.fws-group-box');
$box.find('.fws-item').appendTo('#fws-unassigned');
$box.remove();
});
// Toggle "hide whole group"
$('#fws-board').on('click', '.fws-hide-group', function(){
var $box = $(this).closest('.fws-group-box');
$box.toggleClass('fws-group-hidden');
$(this).find('.dashicons')
.toggleClass('dashicons-visibility', !$box.hasClass('fws-group-hidden'))
.toggleClass('dashicons-hidden', $box.hasClass('fws-group-hidden'));
});
$('#fws-mm-form').on('submit', function(){
var groups = [];
$('#fws-board .fws-group-box').each(function(){
if ($(this).hasClass('fws-unassigned-box') || $(this).hasClass('fws-hidden-box')) return;
var items = [];
$(this).find('.fws-item').each(function(){ items.push($(this).data('slug')); });
groups.push({
label: $(this).find('.fws-group-name').val() || '',
items: items,
hidden: $(this).hasClass('fws-group-hidden')
});
});
var hidden = [];
$('#fws-hidden .fws-item').each(function(){ hidden.push($(this).data('slug')); });
$('#fws_config_json').val(JSON.stringify({ groups: groups, hidden: hidden }));
});
});
</script>
<?php
}
/* ================================================================
* SIDEBAR STYLES
* ================================================================*/
public function group_styles() {
?>
<style>
#adminmenu li.fws-menu-group { margin-top:14px; pointer-events:none; }
#adminmenu li.fws-menu-group > a,
#adminmenu li.fws-menu-group > a:hover,
#adminmenu li.fws-menu-group.menu-top:hover > a,
#adminmenu li.fws-menu-group > a:focus {
background:transparent !important; color:#8a8f94 !important; box-shadow:none !important;
cursor:default; padding-top:10px; border-top:1px solid rgba(255,255,255,0.13);
}
#adminmenu li.fws-menu-group .wp-menu-name {
font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.08em; color:#8a8f94 !important; padding-left:14px;
}
#adminmenu li.fws-menu-group .wp-menu-image { display:none; }
.folded #adminmenu li.fws-menu-group { display:none; }
</style>
<?php
}
}
new FWS_Menu_Manager();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment