Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I'm developing a child-theme using Wordpress 3.4.2 and the development version of the Options Framework by David Price. This is my first theme and I'm relatively new to this, so I've had a look into the Wordpress Codex and checked out registering items into the API.

Without tampering with any external files outside of my theme, I was wondering if there was a way to re-arrange where the Theme Options page is located within the hierarchy of the Appearance menu - so when my theme is activated, the position is not like the first image but like the second.

oldnew

I know you can create either a menu (such as the Appearance tab, Plugins, Users etc.) or a sub-menu (Themes, Widgets, Menus etc.), but how would I go about setting a sub-menu say, second from the top?

From what I gather, somewhere there is an order being called and any other additional pages within the functions.php file are placed after those?

In my functions.php file:

// Add our "Theme Options" page to the Wordpress API admin menu.
if ( !function_exists( 'optionsframework_init' ) ) {
    define( 'OPTIONS_FRAMEWORK_DIRECTORY', get_template_directory_uri() . '/inc/' );
    require_once dirname( __FILE__ ) . '/inc/options-framework.php';
}

Thanks.

share|improve this question
    
Did you try the updated function? –  userabuser Mar 8 '13 at 13:39
    
Thanks for getting back to me @userabuser. I've copy-pasted your updated script and it seems to move the items up and down the list without overriding any others...however, with the new update, I'm still receiving a few errors on the Widgets menu. Warning: Invalid argument supplied for foreach() in /wp-content/themes/mythemename/functions.php on line 1444 Line 1444: foreach ($submenu[$menus] as $index => $value){ and Warning: ksort() expects parameter 1 to be array, null given in /wp-content/themes/mythemename/functions.php on line 1468 Line 1468: ksort($submenu[$menus]); –  user1752759 Mar 8 '13 at 14:19
    
If you could please take a look into this, that would be great. –  user1752759 Mar 8 '13 at 14:21
add comment

3 Answers

Here's an example;

First to figure out the order of the sub menu items based upon its array key you can do a var_dump on the $submenu global variable which will output the following;

(I'm using the Posts menu and sub menu as an example)

  //shortened for brevity....

  ["edit.php"]=>
  array(6) {
    [5]=>
    array(3) {
      [0]=> string(9) "All Posts"
      [1]=> string(10) "edit_posts"
      [2]=> string(8) "edit.php"
    }
    [10]=>
    array(3) {
      [0]=> string(7) "Add New"
      [1]=> string(10) "edit_posts"
      [2]=> string(12) "post-new.php"
    }
    [15]=>
    array(3) {
      [0]=> string(10) "Categories"
      [1]=> string(17) "manage_categories"
      [2]=> string(31) "edit-tags.php?taxonomy=category"
    }
    [17]=>
    array(3) {
      [0]=> string(14) "Sub Menu Title"
      [1]=> string(10) "edit_posts"
      [2]=> string(17) "sub_menu_page.php"
    }
  }

We can see that my sub menu item gets added into the array with a key of 17 after the default items.

If for example I want to add my sub menu item, directly after the All Posts sub menu item I need to do so by setting my array key to either 6, 7, 8 or 9 (anything after 5 and before 10 respectively.

This is how you do it...

function change_submenu_order() {

    global $menu;
    global $submenu;

     //set our new key
    $new_key['edit.php'][6] = $submenu['edit.php'][17];

    //unset the old key
    unset($submenu['edit.php'][17]);

    //get our new key back into the array
    $submenu['edit.php'][6] = $new_key['edit.php'][6];


    //sort the array - important! If you don't the key will be appended
    //to the end of $submenu['edit.php'] array. We don't want that, we
    //our keys to be in descending order
    ksort($submenu['edit.php']);

}

Result,

  ["edit.php"]=>
  array(6) {
    [5]=>
    array(3) {
      [0]=> string(9) "All Posts"
      [1]=> string(10) "edit_posts"
      [2]=> string(8) "edit.php"
    }
    [6]=>
    array(3) {
      [0]=> string(14) "Sub Menu Title"
      [1]=> string(10) "edit_posts"
      [2]=> string(17) "sub_menu_page.php"
    }
    [10]=>
    array(3) {
      [0]=> string(7) "Add New"
      [1]=> string(10) "edit_posts"
      [2]=> string(12) "post-new.php"
    }
    [15]=>
    array(3) {
      [0]=> string(10) "Categories"
      [1]=> string(17) "manage_categories"
      [2]=> string(31) "edit-tags.php?taxonomy=category"
    }
  }

...give it a try and let us know how you go!

Update 1:

Add this to your functions.php file;

function change_post_menu_label() {

    global $menu;
    global $submenu;

    $my_menu  = 'example_page'; //set submenu page via its ID
    $location = 1; //set the position (1 = first item etc)
    $target_menu = 'edit.php'; //the menu we are adding our item to

    /* ----- do not edit below this line ----- */


    //check if our desired location is already used by another submenu item
    //if TRUE add 1 to our value so menu items don't clash and override each other
    $existing_key = array_keys( $submenu[$target_menu] );
    if ($existing_key = $location)
    $location = $location + 1;

    $key = false;
    foreach ( $submenu[$target_menu] as $index => $values ){

        $key = array_search( $my_menu, $values );

        if ( false !== $key ){
            $key = $index;
            break;
        }
    }

     $new['edit.php'][$location] = $submenu[$target_menu][$key];
     unset($submenu[$target_menu][$key]);
     $submenu[$target_menu][$location] = $new[$target_menu][$location];

    ksort($submenu[$target_menu]);

}

My update includes a slightly easier way to handle the setting of your menu position, you need only stipulate the name of your submenu page and the position you want within the menu. However if you select a submenu page $location equal to that of an existing key, it will override that key with yours, thus the menu item will disappear with your menu item in its place. Increment or decrement the number to correctly order your menu if that is the case. Similar, if someone installs a plugin that effects that same menu area, and for which has the same $location as your submenu item then the same problem will occur. To circumvent that, Kaiser's example provides some basic checking for that.

Update 2:

I've added an additional block of code that checks all existing keys in the array against our desired $location and if a match is found we will increment our $location value by 1 in order to avoid menu items overriding each other. This is the code responsible for that,

   //excerpted snippet only for example purposes (found in original code above)
   $existing_key = array_keys( $submenu[$target_menu] );
   if ($existing_key = $location)
   $location = $location + 1;

Update 3: (script revised to allow sorting of multiple sub menu items)

add_action('admin_init', 'move_theme_options_label', 999);

function move_theme_options_label() {
    global $menu;
    global $submenu;

$target_menu = array(
    'themes.php' => array(
        array('id' => 'optionsframework', 'pos' => 2),
        array('id' => 'bp-tpack-options', 'pos' => 4),
        array('id' => 'multiple_sidebars', 'pos' => 3),
        )
);

$key = false;

foreach ( $target_menu as $menus => $atts ){

    foreach ($atts as $att){

        foreach ($submenu[$menus] as $index => $value){

        $current = $index;  

        if(array_search( $att['id'], $value)){ 
        $key = $current;
        }

            while (array_key_exists($att['pos'], $submenu[$menus]))
                $att['pos'] = $att['pos'] + 1;

            if ( false !== $key ){

                if (array_key_exists($key, $submenu[$menus])){
                    $new[$menus][$key] = $submenu[$menus][$key];
                    unset($submenu[$menus][$key]);
                    $submenu[$menus][$att['pos']] = $new[$menus][$key];

                } 
            }
        }
    }
}

ksort($submenu[$menus]);
return $submenu;

}

In the example above you can target multiple sub menus and multiple items per sub menu by setting the parameters accordingly within the $target_menu variable which holds a multi-dimensional array of values.

$target_menu = array(
//menu to target (e.g. appearance menu)
'themes.php' => array(
    //id of menu item you want to target followed by the position you want in sub menu
    array('id' => 'optionsframework', 'pos' => 2),
    //id of menu item you want to target followed by the position you want in sub menu
    array('id' => 'bp-tpack-options', 'pos' => 3),
    //id of menu item you want to target followed by the position you want in sub menu
    array('id' => 'multiple_sidebars', 'pos' => 4),
    )
 //etc....
);

This revision will prevent sub menu items over-writing each other if they have the same key (position), as it will cycle through until it finds an available key (position) that does not exist.

share|improve this answer
    
Thanks for the quick response userabuser, but I'm fairly new to all of this, so please just bear with me. I'm not exactly sure how to implement your script/code above and which file it needs to be placed in because of how concise its been written - please elaborate. With this example working though and outputting the number needed...if the user was to install a plugin later on that created an additional top-level menu with a few sub-levels inside (such as an e-commerce solution), will this effect the array key and brake what its been set out to do? –  user1752759 Oct 30 '12 at 13:49
1  
@Rob Made a slight adjustment that should help avoid situations where menu items override each other. –  userabuser Oct 30 '12 at 15:42
    
@user1752759 What does this have to do with the above? What is the full path to the functions.php file that you provide in your above comment? What is the code within that file? At last conversation this worked for you. It also works for me. So I am suspecting that something else here may be a miss within your code, if I recall correctly last time you duplicated two snippets of code and did not have the correct braces around your function. –  userabuser Nov 7 '12 at 11:09
    
Thanks for getting back to me @userabuser. I've copy-pasted your updated script and it seems to move the items up and down the list without overriding any others...however, with the new update, I'm still receiving a few errors on the Widgets menu. Warning: Invalid argument supplied for foreach() in /wp-content/themes/mythemename/functions.php on line 1444 Line 1444: foreach ($submenu[$menus] as $index => $value){ and Warning: ksort() expects parameter 1 to be array, null given in /wp-content/themes/mythemename/functions.php on line 1468 Line 1468: ksort($submenu[$menus]); –  user1752759 Mar 20 '13 at 3:00
    
If you could please take a look into this, that would be great. –  user1752759 Mar 20 '13 at 3:01
add comment

The admin menu (and its problems)

As the admin menu seriously lacks any hooks and a public API (that allow moving the items around), you have to use some workarounds. The following answer shows you what is awaiting you in the future and how you can work around as long as we have the current state of core.

First I have to note, that scribu is working on an admin menu patch that should make the handling much easier. The current structure is pretty messed up and I have written an article about it that will soon be outdated. Expect WP 3.6 to change things completely.

Then there's also the point, that you shouldn't use Options pages for themes anymore. There's - nowadays - the »Theme Customizer« for that.

The plugin

I wrote a plugin that tests this with the default "Theme Options" page for the TwentyEleven/Ten options page. As you can see, there's no real API that allows any position. So we have to intercept the global.

In short: Just follow the comments and take a look at the admin notices, that I added to give you some debug output.

<?php
/** Plugin Name: (#70916) Move Submenu item */

add_action( 'plugins_loaded', array( 'wpse70916_admin_submenu_items', 'init' ) );

class wpse70916_admin_submenu_items
{
    protected static $instance;

    public $msg;

    public static function init()
    {
        is_null( self :: $instance ) AND self :: $instance = new self;
        return self :: $instance;
    }

    public function __construct()
    {
        add_action( 'admin_notices', array( $this, 'add_msg' ) );

        add_filter( 'parent_file', array( $this, 'move_submenu_items' ) );
    }

    public function move_submenu_items( $parent_file )
    {
        global $submenu;
        $parent = $submenu['themes.php'];

        $search_for = 'theme_options';

        // Find current position
        $found = false;
        foreach ( $parent as $pos => $item )
        {
            $found = array_search( $search_for, $item );
            if ( false !== $found )
            {
                $found = $pos;
                break;
            }
        }
        // DEBUG: Tell if we didn't find it.
        if ( empty( $found ) )
            return $this->msg = 'That search did not work out...';

        // Now we need to determine the first and second item position
        $temp = array_keys( $parent );
        $first_item  = array_shift( $temp );
        $second_item = array_shift( $temp );

        // DEBUG: Check if it the item fits between the first two items:
        $distance = ( $second_item - $first_item );
        if ( 1 >= $distance )
            return $this->msg = 'We do not have enough space for your item';

        // Temporary container for our item data
        $target_data = $parent[ $found ];

        // Now we can savely remove the current options page
        if ( false === remove_submenu_page( 'themes.php', $search_for ) )
            return $this->msg = 'Failed to remove the item';

        // Shuffle items (insert options page)
        $submenu['themes.php'][ $first_item + 1 ] = $target_data;
        // Need to resort the items by their index/key
        ksort( $submenu['themes.php'] );
    }

    // DEBUG Messages
    public function add_msg()
    {
        return print sprintf(
             '<div class="update-nag">%s</div>'
            ,$this->msg
        );
    }
} // END Class wpse70916_admin_submenu_items

Good luck and have fun.

share|improve this answer
add comment

Custom filters

There is be another possibility to achieve this. Don't ask me why I haven't thought earlier about it. Anyway, there's a filter dedicated to a custom menu order. Simply set it to true to allow a custom order. Then you got a second hook to order the main menu items. In there we just intercept the global $submenu and switch around our sub menu items.

enter image description here

This example moves the Menus item above the Widgets item to demonstrate its functionality. You can adjust it to what you like.

<?php
defined( 'ABSPATH' ) OR exit;
/**
 * Plugin Name: (#70916) Custom Menu Order
 * Description: Changes the menu order of a submenu item.
 */

// Allow a custom order
add_filter( 'custom_menu_order', '__return_true' );
add_filter( 'menu_order', 'wpse70916_custom_submenu_order' );
function wpse70916_custom_submenu_order( $menu )
{
    // Get the original position/index
    $old_index = 10;
    // Define a new position/index
    $new_index = 6;

    // We directly interact with the global
    $submenu = &$GLOBALS['submenu'];
    // Assign our item at the new position/index
    $submenu['themes.php'][ $new_index ] = $submenu['themes.php'][ $old_index ];
    // Get rid of the old item
    unset( $submenu['themes.php'][ $old_index ] );
    // Restore the order
    ksort( $submenu['themes.php'] );

    return $menu;
}
share|improve this answer
    
I'm not very confident when it comes to using PHP @kaiser, but would you possibly know of a way to implement the script above to include multiple items within the same function wpse70916_custom_submenu_order( $menu ) to say, re-order not just the Menu, but also the Theme Options, Widgets, Editor etc. making it quite flexible and also so that items don't override each other? Thank you. –  user1752759 Mar 21 '13 at 1:46
    
@user1752759 The plugin already has this flexibility. Conflict safeness (avoid overriding) is another issue. This won't be possible in a 100% scenario as you can't assign your action last. There's always something that can run later. Anyway: Please open a new question and link to this one. –  kaiser Mar 21 '13 at 1:58
    
thank you and will do kaiser. if it's not too much to ask, could you please update the script above to show how multiple items are done (eg. the Menu and the Widgets), which I should be able to use as a guide for doing the same to other items? Being fairly new to PHP, I don't think I'm doing it correctly, perhaps because of the numbers. cheers –  user1752759 Mar 21 '13 at 2:09
    
Please just ask a new question and link to this one. Thanks. –  kaiser Mar 21 '13 at 2:13
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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