Hide Theme options and Customize Admin menu

Under the appearance admin menu, I have customizer added by the theme, and theme options added by a plugin. I’m using this code to hide both menus ( submenu’s of Appearance )for ALL admins apart from a certain USERNAME.

function hide_menu() {
    global $current_user;
    $current_user = wp_get_current_user();
    $user_name = $current_user->user_login; 

        //check condition for the user means show menu for this user
        if(is_admin() &&  $user_name != 'USERNAME') {

         remove_submenu_page( 'themes.php', 'customize' );
         remove_submenu_page( 'themes.php', 'core-settings' );
   }
}
add_action('admin_head', 'hide-menu');

The code its self works fine, I can hide parent menu’s. But I can’t seem to hide the two sub menu’s that I want to hide.

The two menu’s point to URLs :

domainname.com/wp-admin/customize.php

domainname.com/wp-admin/themes.php?page=core-settings

Submenu Debug :

[6] => Array
                (
                    [0] => Customise
                    [1] => customize
                    [2] => customize.php?return=%2Fwp-admin%2Findex.php
                    [3] => 
                    [4] => hide-if-no-customize
                )
               [21] => Array
                (
                    [0] => Theme Settings
                    [1] => manage_options
                    [2] => core-settings
                    [3] => Theme Settings
                )

I’ve looked at dubug mode but im not sure what im looking at, could someone please give a solution and an explanation as to why it works.

2 Answers
2

Direct answer:

add_action( 'admin_menu', function() {
    global $current_user;
    $current_user = wp_get_current_user();
    $user_name = $current_user->user_login;

        //check condition for the user means show menu for this user
        if(is_admin() &&  $user_name != 'USERNAME') {
            //We need this because the submenu's link (key from the array too) will always be generated with the current SERVER_URI in mind.
            $customizer_url = add_query_arg( 'return', urlencode( remove_query_arg( wp_removable_query_args(), wp_unslash( $_SERVER['REQUEST_URI'] ) ) ), 'customize.php' );
            remove_submenu_page( 'themes.php', $customizer_url );
   }
}, 999 );

You have to respect the full-path naming.


The long answer, this:

add_action( 'admin_menu', function() {
    $page = remove_submenu_page( 'themes.php', 'customize.php' );
}, 999 );

Doesn’t work. But this:

add_action( 'admin_menu', function() {
    $page = remove_submenu_page( 'themes.php', 'widgets.php' );
}, 999 );

Works. If we just look at the links, then we can see that it should work, but check this out. In wp-admin/menu.php, line 164, we have:

$submenu['themes.php'][6] = array( __( 'Customize' ), 'customize', esc_url( $customize_url ), '', 'hide-if-no-customize' );

If we comment this out, bang, the Customize link dissapears, but if we go on ahead and paste this code and go to our wp-admin/index.php, we can see that there’s no customize.php in the array:

add_action( 'admin_menu', function() {
   global $submenu;
   var_dump( $submenu );
}, 999 );

The others are here. So what’s going on? If we dump the $submenu right after it’s created, we can see that it’s there:

[themes.php] => Array
    (
        [5] => Array
            (
                [0] => Themes
                [1] => switch_themes
                [2] => themes.php
            )

        [6] => Array
            (
                [0] => Customize
                [1] => customize
                [2] => customize.php?return=%2Fwordpress%2Fwp-admin%2Findex.php
                [3] => 
                [4] => hide-if-no-customize
            )

        [10] => Array
            (
                [0] => Menus
                [1] => edit_theme_options
                [2] => nav-menus.php
            )

    )

…so, somewhere, it gets lost, or so you would think, at the end of this file, we see that, yet again, we include another file:

require_once(ABSPATH . 'wp-admin/includes/menu.php');

If we then go at the end of this file and we dump the $menu, what we get is..surprisingly, a menu without ‘customizer’ in it. But it was there just moments ago.

Surprisingly, if we do:

add_action( 'admin_menu', function() {
    global $submenu;
    var_dump( $submenu );
}, 999 );

It’s clear, the customize.php is there…except…it generates itself with some parameters after: customize.php?return=%2Fwordpress%2Fwp-admin%2Findex.php (this depends on your site).

Interesting, so if we do:

add_action( 'admin_menu', function() {
    remove_submenu_page( 'themes.php', 'customize.php?return=%2Fwordpress%2Fwp-admin%2Findex.php' );
}, 999 )

Or, in your case:

add_action( 'admin_menu', function() {
    remove_submenu_page( 'themes.php', 'customize.php?return=%2Fwp-admin%2Findex.php' );
}, 999 )

It works.

So what did we learn today? WP Core is a bad piece of crap, ok, it’s true but it’s your mistake for not looking at what remove_submenu_page looks for.

function remove_submenu_page( $menu_slug, $submenu_slug ) {
    global $submenu;

    if ( !isset( $submenu[$menu_slug] ) )
        return false;

    foreach ( $submenu[$menu_slug] as $i => $item ) {
        if ( $submenu_slug == $item[2] ) {
            unset( $submenu[$menu_slug][$i] );
            return $item;
        }
    }

    return false;
}

It first checks to see if the menu exists, in our case themes.php and then it goes over each item from, say, Appearance, each being an array itself, then it looks for the third item, which corresponds to the [2] => customize.php... item, so, of course it’s not going to find our customize.php, that’s why we need to provide it the full link. It’s just a simple mistake.

Leave a Comment