4

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.

1
  • i help you please wait Commented Nov 15, 2018 at 13:34

2 Answers 2

13
+50

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.

4
  • Perfect thank you. I can award the bounty in 15 hours so I will do so then :)
    – Randomer11
    Commented Nov 18, 2018 at 18:46
  • @coolpasta upvoted, plus you win the prize for most extensive debugging explanation ever... good work!
    – majick
    Commented Nov 19, 2018 at 12:30
  • @Randomer11 No problem. Also majick, I found out that my "research" was futile after discovering that it's just actually as simple as that. Sometimes the answer is right in front of us but due to lack of experience, we think there's more..."can't be that simple".
    – coolpasta
    Commented Nov 19, 2018 at 17:03
  • 1
    Try remove_submenu_page( 'themes.php', 'customize.php?return=' . urlencode($_SERVER['SCRIPT_NAME']));
    – FooBar
    Commented Sep 28, 2019 at 15:38
6

This would work in WordPress 5

add_action( 'admin_menu', function() {
    remove_submenu_page( 'themes.php', 'customize.php?return=' . urlencode($_SERVER['SCRIPT_NAME']));
});
1
  • 1
    Use $_SERVER['REQUEST_URI'] instead, else the Customizer option will still appear if the url of the current admin page includes a query string
    – Andy P
    Commented Jan 3 at 16:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.