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.

This question is a bit unique.

It is in part a "challenge" I'm issuing to the WordPress team (or anyone else) related to trac tickets: #16048, #16050 and #16204.

The Goal

The goal is to get around the three (3) issues illustrated in the screenshot below when trying to modify the WordPress Admin menu section:

  1. Get the "Microsite" submenu page to be highlighted when editing an Attorney (for this we need to somehow be able to apply "current" to the submenu item,_ and some hooks in the _wp_menu_output() function would provide what's needed here),

  2. Get the Attorney Menu Page link to link to /wp-admin/edit.php?post_type=attorney when editing an Attorney (and those same needed hooks in the _wp_menu_output() function could handle this), and

  3. Get the "Microsite" link not to trigger a "You do not have sufficient permissions to access this page" error *(this is the nastiest one to resolve, and a hook on the return value of user_can_access_admin_page() could handle this issue nicely.)

Screenshot for The Great WordPress Admin Menu Challenge of Jan 2011

More than just my use-case

These three (3) issues are for my use-case but their are emblematic of the issues related to configure the admin menus in WordPress.

Several on the WordPress team said that it's easy and thus implied I'm missing something (which may be right) but I've looked at this problem for weeks and not figured out how to get around it so I created the plugin you see below (also downloadable from Gist) as the simplest use-case example of the issues. The code in admin_menu2() is rather hackish but that's pretty much what's required to modify the Admin Menus in WordPress.

Note that I did not try to use the new remove_menu_page() nor the new remove_submenu_page() functions in 3.1 because it would have taken longer to create the plugin -- I already had the code in admin_menu2() from an existing project -- and I don't believe they would address the problem anyway.

What do I need?

I need one of two (2) things:

  1. A solution to the problems that I expose with this plugin and explain in this question and in the screenshot (BTW, I'll disqualify your solution if you use PHP Output Buffering to solve any part of this), or

  2. To have the WordPress Team recognize that there is indeed a need for these hooks and to get them to reconsider their position on the tickets.

How do you Take the Challenge?

  1. Download and install a pristine new copy of WordPress 3.1 (any revision will probably do),

  2. Download, install and activate the "The Great WordPress Admin Menu Challenge of Jan 2011" plugin below (or download the plugin from Gist), and then

  3. Follow the instructions found on the plugin page for this plugin (see the following screenshot) but basically load the screenshot you see above and then try to figure out the three (3) issues described:

Screenshot of "The Great WordPress Admin Menu Challenge of Jan 2011" plugin instructions

One Ray of Hope

Fortunately Andrew Nacin of the WordPress team offered to look at this once I'd coded it so I'm primarily posting here for him to review and comment as well as having others to comment. I know he is busy but I do hope he (or even you) can take the time to install this plugin on a pristine install of v3.1 and see if he can resolve the issue.

If you Agree the Challenge is Impossible?

If after trying this challenge you come to the same conclusion as me, and if you'd like to see the WordPress Admin menus be more configurable please comment on these trac tickets (#16048 - #16050 - #16204) and vote this question up to show support for it.

I'll Gladly Admit I Missed Something, If I Did

Of course it's possible I could be completely brain dead on this and someone could point out exactly how to do it. Actually, I really hope that ends up being the case; I'd rather be wrong and have this working than vice-versa.

And Here's the Plugin

You can also downloadable if from Gist:

<?php
/*
Plugin Name: The Great WordPress Admin Menu Challenge of Jan 2011
Description: <em>"The Great WordPress Admin Menu Challenge of Jan 2011"</em> was inspired by the WordPress team's apparent lack of understanding of the problems addressed by trac tickets <a href="http://core.trac.wordpress.org/ticket/16048">#16048</a> and <a href="http://core.trac.wordpress.org/ticket/16050">#16050</a> <em>(See also: <a href="http://core.trac.wordpress.org/ticket/16204">#16204</a>)</em> and suggestion that the <a href="http://wordpress.org/extend/plugins/admin-menu-editor/>Admin Menu Editor</a> plugin handles the use-cases that the tickets address. Debate spilled over onto Twitter with participation from <a href="http://twitter.com/nacin">@nacin</a>, <a href="http://twitter.com/aaronjorbin">@aaronjorbin</a>, <a href="http://twitter.com/petemall">@petemall</a>, <a href="http://twitter.com/westi">@westi</a>, <a href="http://twitter.com/janeforshort">@janeforshort</a>, <a href="http://twitter.com/PatchesWelcome">@PatchesWelcome</a>; supportive comments from <a href="http://twitter.com/ramsey">@ramsey</a>, <a href="http://twitter.com/brianlayman">@brianlayman</a>, <a href="http://twitter.com/TheLeggett">@TheLeggett</a>, a retweeting of @nacin's simple yet <em>(AFAICT)</em> insufficient solution by <a href="http://twitter.com/vbakaitis">@vbakaitis</a>, <a href="http://twitter.com/Viper007Bond">@Viper007Bond</a>, <a href="http://twitter.com/nickopris">@nickopris</a>, <a href="http://twitter.com/Trademark">@Trademark</a>, <a href="http://twitter.com/favstar_pop">@favstar_pop</a>, <a href="http://twitter.com/designsimply">@designsimply</a>, <a href="http://twitter.com/darylkoop">@darylkoop</a>, <a href="http://twitter.com/iamjohnford">@iamjohnford</a>, <a href="http://twitter.com/markjaquith">@markjaquith</a>, <a href="http://twitter.com/JohnJamesJacoby">@JohnJamesJacoby</a> and <a href="http://twitter.com/dd32">@dd32</a>. Also see <a href="http://andrewnacin.com/2010/12/20/better-admin-menu-controls-custom-post-types-wordpress-3-1/#comment-6360">comments</a> on @nacin's blog post entitled "<em>Better admin menu handling for post types in WordPress 3.1</em>." <strong>The desired goal of the <em>"challenge"</em></strong> is to simply either to find a solution that has eluded me or, to get those who are dismissing it as solvable without added hooks in WordPress to have a tangible example to explore in hopes they will recognize that there is indeed a need for at least some of the requested hooks. <strong>There are three (3) steps to the challenge:</strong> 1.) Get the "Microsite" submenu page to be highlighted when editing an Attorney, 2.) Get the Attorney Menu Page link to link <a href="/wp-admin/edit.php?post_type=attorney">here</a>  when editing an Attorney, and 3.) Get the "Microsite" link not to trigger a "You do not have sufficient permissions to access this page" error.  Here is <a href="http://mikeschinkel.com/websnaps/skitched-20110114-235302.png" target="_blank"><strong>a screenshot</strong> that attempts to illustrate the callenge</a>. The code can be found on gist <a href="https://gist.github.com/780709"><strong>here</strong></a>. Activate it as a plugin in a WordPress 3.1 install and go <a href="/wp-admin/post.php?post=10&action=edit"><strong>here</strong></a> to see what the screenshot illustrates. <strong>Be sure to load the <a href="http://mikeschinkel.com/websnaps/skitched-20110114-235302.png" target="_blank">screenshot</a> in another browser tab or window first</strong>.
Author:      Mike Schinkel
Author URI:  http://about.me/mikeschinkel
Plugin URI:  https://gist.github.com/780709
*/
if (!class_exists('TheGreatWordPressAdminMenuChallenge')) {
  class TheGreatWordPressAdminMenuChallenge {
    static function on_load() {
      add_action('init',array(__CLASS__,'init'));
      add_action('admin_menu',array(__CLASS__,'admin_menu1'));      // Simulates generic "Microsite" plugin
      add_action('admin_menu',array(__CLASS__,'admin_menu2'),100);  // Simulates website-specific plugin
      add_action('post_row_actions',array(__CLASS__,'post_row_actions'),10,2);
    }
    static function post_row_actions($actions,$post) {
      $url = admin_url(self::this_microsite_url($post->ID));
      $actions = array_merge(array('microsite'=>"<a href=\"{$url}\" title=\"Manage this Microsite\">Microsite</a>"),$actions);
      return $actions;
    }
    static function the_microsite_editor() {
      echo "We are in the Microsite Editor for " . self::post_title();
    }
    static function admin_menu1() {
      if (self::this_post_id() && in_array(self::this_post_type(),array('attorney','practice_area'))) {
        add_submenu_page(
          self::this_parent_slug(),
          self::microsite_page_title(),
          self::microsite_page_title(),
          $capability = 'edit_posts',
          'microsite',
          array($microsite,'the_microsite_editor')
        );
        global $wp_post_types;
        $parent_type_meta = $wp_post_types[self::this_post_type()];
        global $menu;
        $slug = false;
        foreach($menu as $index => $menu_page)
          if ($menu_page[0]===$parent_type_meta->label) {
            $slug = $menu_page[2];
            break;
          }
        if ($slug) {
          global $pagenow;
          global $submenu;
          // Setting this makes gives the link to the microsite in the menu the highlight for "current" menu option
          global $submenu_file;
          $submenu_file = self::this_microsite_url();
          $index = end(array_keys($submenu[$slug]));
          $submenu[$slug][$index][12] = $submenu_file;
        }
      }
    }
    static function this_parent_slug() {
      return "edit.php?post_type=" . self::this_post_type();
    }
    static function post_title() {
      $post_id = self::this_post_id();
      return ($post_id ? get_post($post_id)->post_title : false);
    }
    static function microsite_page_title() {
      return 'Microsite for ' . self::post_title();
    }
    static function this_post_type($get_post=true) {
      $post_type = (isset($_GET['post_type']) ? $_GET['post_type'] : false);
      if (!$post_type && $get_post) {
        $post_id = self::this_post_id();
        $post_type = get_post($post_id)->post_type;
      }
      return $post_type;
    }
    static function this_post_id() {
      $post_id = false;
      $post_type = self::this_post_type(false);
      if (isset($_GET[$post_type]))
        $post_id = intval($_GET[$post_type]);
      else if (isset($_GET['post']))
        $post_id = intval($_GET['post']);
      return $post_id;
    }
    static function this_microsite_url($post_id=false) {
      $post_type = self::this_post_type();
      $post_id = $post_id ? intval($post_id) : self::this_post_id();
      return "edit.php?post_type={$post_type}&page=microsite&attorney={$post_id}";
    }
    static function admin_menu2() {
      // The code required for this is super, nasty, ugly and shouldn't be, but at least it *is* doable
      global $menu;
      global $submenu;
      global $microsite;

      $parent_type = self::this_post_type();
      foreach(array('attorney','practice_area') as $post_type) {
        $slug = "edit.php?post_type={$post_type}";
        if ($post_type==$parent_type) {  // If a microsite remove everything except the microsite editor
          $microsite_url = self::this_microsite_url();
          foreach($submenu[$slug] as $submenu_index => $submenu_page) {
            if ($submenu_page[2]!=$microsite_url) {
              unset($submenu[$slug][$submenu_index]);
            }
          }
        } else {
          $submenu[$slug] = array();
        }
      }

       // Remove the Submenus for each menu
      unset($submenu['index.php']);
      unset($submenu['edit.php?post_type=article']);
      unset($submenu['edit.php?post_type=event']);
      unset($submenu['edit.php?post_type=case_study']);
      unset($submenu['edit.php?post_type=news_item']);
      unset($submenu['edit.php?post_type=transaction']);
      unset($submenu['edit.php?post_type=page']);
      unset($submenu['upload.php']);

      unset($submenu['users.php'][13]); // Removed the "Add New"

      $remove = array_flip(array(
        'edit.php',
        'link-manager.php',
        'edit-comments.php',
        'edit.php?post_type=microsite-page',
      ));
      if (!current_user_can('manage_tools'))
        $remove['tools.php'] = count($remove);

      foreach($menu as $index => $menu_page) {
        if (isset($remove[$menu_page[2]])) {
          unset($submenu[$menu_page[2]]);
          unset($menu[$index]);
        }
      }

      $move = array(
        'edit.php?post_type=page' => array( 'move-to' => 35,  0 => 'Other Pages' ),
        'separator2' => array( 'move-to' => 40 ),
        'upload.php' => array( 'move-to' => 50, 0 => 'Media Library' ),
      );
      $add = array();
      foreach($menu as $index => $menu_page) {
        if (isset($move[$menu_page[2]])) {
          foreach($move[$menu_page[2]] as $value_index => $value) {
            if ($value_index==='move-to') {
              $move_to = $value;
            } else {
              $menu_page[$value_index] = $value;
            }
          }
          $add[$move_to] = $menu_page;
          unset($menu[$index]);
        }
      }
      foreach($add as $index => $value)
        $menu[$index] = $value;

      add_menu_page(
        'Attorney Positions',
        'Attorney Positions',
        'edit_posts',
        'edit-tags.php?taxonomy=attorney-position&amp;post_type=attorney',
        false,
        false,
        55);

      ksort($menu); // Need to sort or it doesn't come out right.
    }
    static function init() {
      register_post_type('attorney',array(
        'label'           => 'Attorneys',
        'public'          => true,
      ));
      register_post_type('practice_area',array(
        'label'           => 'Practice Areas',
        'public'          => true,
      ));
      register_taxonomy('attorney-position','attorney',array(
        'label'=>'Attorney Positions',
      ));
      register_post_type('article',array(
        'label'           => 'Articles & Presentations',
        'public'          => true,
      ));
      register_post_type('case_study',array(
        'label'           => 'Case Studies',
        'public'          => true,
      ));
      register_post_type('news_item',array(
        'label'           => 'Firm News',
        'public'          => true,
      ));
      register_post_type('event',array(
        'label'           => 'Events',
        'public'          => true,
      ));
      register_post_type('transaction',array(
        'label'           => 'Transactions',
        'public'          => true,
      ));

      // Install the test data
      $post_id = 10;
      $attorney = get_post($post_id);
      if (!$attorney) {
        global $wpdb;
        $wpdb->insert($wpdb->posts,array(
          'ID' => $post_id,
          'post_title' => 'John Smith',
          'post_type' => 'attorney',
          'post_content' => 'This is a post about the Attorney John Smith.',
          'post_status' => 'publish',
          'post_author' => 1,
        ));
      }
    }
  }
  TheGreatWordPressAdminMenuChallenge::on_load();
}

To all who read this, I'm really hoping you can help.

Thanks in advance.

share|improve this question
    
I am interested (and really need to level up my experience with admin side), but will probably wait for 3.1 final release. My local test stack isn't very suited for multiple core versions so I stick with current stable. –  Rarst Jan 15 '11 at 17:32
    
I know exactly the problem you speak of Mike, i don't think i could describe it any better than you have there, but i've seen the very same issues when writing a dropdown menu for the admin(for fun), just adding my +1. –  t31os Jan 17 '11 at 2:11
    
@t310s - Thanks for adding your +1. It took me probably 2 weeks of research to be able to describe the problem, so that fact you can even recognize that it's relevant (and haven't spent the 2 weeks I have) means you are way ahead of most everyone on this, including me! –  MikeSchinkel Jan 17 '11 at 3:00

2 Answers 2

up vote 2 down vote accepted

Mike, I've taken a look at the code and your ideal end use case ... and some of them, frankly, aren't possible with the current system. Again, your requirements:

  1. Get the "Microsite" submenu page to be highlighted when editing an Attorney
  2. Get the Attorney Menu Page link to link to /wp-admin/edit.php?post_type=attorney when editing an Attorney
  3. Get the "Microsite" link not to trigger a "You do not have sufficient permissions to access this page" error

And the key issue here is #2.

What I tried

I tried adding a custom post type for Attorneys and was immediately reminded that /wp-admin/edit.php?post_type=attorney will give you a list of attorneys, not an actual edit screen. The actual editing take place on /wp-admin/post.php?post=10&action=edit. So if you're really tied to #2 ... the other two criteria won't work.

This is why #3 fails in implementation ... and I wasn't even able to attempt #1 because I couldn't get that far.

share|improve this answer
    
Believe your analysis is correct. The layout shown in question is one I've had more than one client request in their goals to simplify the menu structures for their use-cases. I've proposed using submenus but they didn't like that; they felt it would be too confusing for their users. One thing I may not have mentioned is that I'm developing a product based on WordPress for them to distribute vs. a WordPress website for them where I can just train them to know how to make things work. Their other option is to abandon WordPress; not what I want them to do. –  MikeSchinkel Jan 16 '11 at 23:11

Hey Mike, your issue #3 is due to you specifying ($microsite, 'the_microsite_editor'), where it should be (__CLASS__, 'the_microsite_editor').

Update: After spending way too much time trying to solve some similar trouble for my own plugin, here's something I found that may help with your Challenge (note that the functions are methods underneath your class):

function add_posttype_submenu_page($mytype, $label, $cap, $slug) {  
    /* we add two submenu pages to work around the 
       edit.php?post_type=...&page=...problem and have 
       our page called as admin.php?page=... instead */
    //first create a 'blind' pseudo-entry to register our page callback
    add_submenu_page($mytype, $label, $label, $cap, $slug, 
                     array( &$this, 'admin_'.$mytype ));
    //then create a real entry that 'calls' our pseudo-entry
    add_submenu_page('edit.php?post_type='.$mytype, $label, 
                     $label, $cap, 'admin.php?page='.$slug);
    /* then lets fix/hack the highlighting */
    global $plugin_page;
    global $submenu_file;
    if ($plugin_page == $slug) {
        // this next line highlights the submenu entry
        $submenu_file = 'admin.php?page='.$slug; 
        add_filter('parent_file', 
                   array(&$this, 'evil_parent_file_hack'));
    }
} 

function evil_parent_file_hack() {
    //we do this to get the parent menu properly highlighted, too
    //it only gets called on the submenu menu page in question
    global $self;
    global $parent_file;
    $self = $parent_file;
    remove_filter('parent_file', array(&$this, 'evil_parent_file_hack'));
}

Then you simply call add_posttype_submenu_page() with the according parameters. This should properly add a submenu item to a menu that was auto-created during a register_post_type() call.

share|improve this answer
    
oops...the double underscores around CLASS were turned into bold formatting ;-) –  wyrfel Feb 12 '11 at 13:44
    
I fixed it. :) –  toscho Feb 12 '11 at 14:26
    
;-) Thanks, toscho. –  wyrfel Feb 12 '11 at 15:52
    
Oh, that is just awesome; thanks! How could I have missed that first point?!? Doh! –  MikeSchinkel Feb 12 '11 at 17:23
    
Thanks, Mike. Back to your original topic...WP internally generates an ID for some menu entries and stores it in value 4 of the menu arrays. Such as the plugin-page-hooks. However, for custom posttypes it stores an ID that is inconsistent with the plugin-page-hook format. I think a whole lot could be helped if WP would make this consistent (i.e. create page hooks for everything and use them to associate menu entries with one another, rather than attaching submenus by means of the 'parent slug/file'. –  wyrfel Feb 13 '11 at 11:59

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.