Upgrading your menu system from 5.x to 6.x

Last modified: August 7, 2008 - 14:20

Cached part

This is very simple, a search-and-replace operation. Here is a list of changes:

  • hook_menu() no longer takes any parameters (remove $may_cache).
  • The value of path is the new index for $items.
  • callback becomes page callback
  • callback arguments becomes page arguments
  • access becomes access callback and access arguments.

    For example, access => user_access('administer nodes') becomes 'access callback' => 'user_access', 'access arguments' => array('administer nodes').

    However, the default for 'access callback' is 'user_access' so you can leave that out.

    Complex access things must be moved to a function which can be called on runtime, user_is_anonymous and user_is_logged_in are useful helpers. See Access control for more.

  • The title and description arguments should not have strings wrapped in t(), because translation of these happen in a later stage in the menu system. This allows translation of menu items to any language required on a site, adapting to the language used on the page.

Non-cached part

Let's suppose you had

<?php
/**
* Implementation of hook_menu().
*/
function aggregator_menu($may_cache) {
  if (!
$may_cache) {
    if (
arg(0) == 'aggregator' && is_numeric(arg(2))) {
      if (
arg(1) == 'sources') {
       
$feed = aggregator_get_feed(arg(2));
        if (
$feed) {
         
$items[] = array('path' => 'aggregator/sources/'. $feed['fid'] .'/configure',
           
'title' => t('Configure'),
           
'callback' => 'drupal_get_form',
           
'callback arguments' => array('aggregator_form_feed', $feed),
           
'access' => user_access('administer news feeds'),
           
'type' => MENU_LOCAL_TASK,
           
'weight' => 1);
        }
      }
    }
  }

  return
$items;
}
?>

this becomes

<?php
/**
* Implementation of hook_menu().
*/
function aggregator_menu() {
 
$items['aggregator/sources/%aggregator_feed/configure'] = array(
   
'title' => 'Configure',
   
'page callback' => 'drupal_get_form',
   
'page arguments' => array('aggregator_form_feed', 2),
   
// NOTE: as of Drupal 6.2, all menu items are *required* to have
    // access control.
   
'access arguments' => array('administer news feeds'),
   
'type' => MENU_LOCAL_TASK,
   
'weight' => 1,
  );

  return
$items;
}
?>

If we would use just a percent sign aggregator/sources/%/configure it'd match everything, including non-numeric values as well, like aggregator/sources/foo/configure. However, with %aggregator_feed we ask for a feed to be loaded based on the third argument and anything that's not a valid feed will lead to a 404.

About the use of wildcards, foreach() loops and MENU_ITEMs

In Drupal 5, we used to use foreach() loops to recursively declare several MENU_ITEMs or MENU_SUGGESTED_ITEMs.

In Drupal 6, we only need one entry in hook_menu() with the use of a proper wildcard. Additionally, you can create as many menu items (enabled or not) as you see fit with menu_link_save().

For a detailed discussion on this topic, see this issue.

Additional Run Once Code

Non menu code that was placed in hook_menu !$may_cache so that it could be run during initialisation, should now be moved to hook_init. Previously we called hook_init twice, once early in the bootstrap process, second just after the bootstrap has finished. The first instance is now called boot instead of init.

If you were using a loop in your menu code then you likely need menu_link_save.

A horribly complex example can be found in the menu of http://drupal.org/files/issues/akismet.d6-port.patch. It involves just about every possible path manipulation trick one could ever need for the path akismet/%akismet/%/'. $op, and then some. It passes the real page callback in $map[0] which is a bit of a hack, but is also quite useful.

Upgrading to version 6 menu system

whiteowl - March 10, 2008 - 02:55

Thanks,

To make the code comparable shouldn't the version 6 be:

  $items['aggregator/sources/%aggregator_get_feed/configure'] = array(

It blows my mind a bit that the 2 in:

     'page arguments' => array('aggregator_form_feed', 2),

is the result of calling a function on the second argument, and so could be a different type (a whole feed) than was in the original URL.

Good on ya!!

PS you might want to put Drupal 6 somewhere in the title

Actually, they are assuming

justindodge - July 7, 2008 - 22:41

Actually, they are assuming you would rename the function to 'aggregator_feed_load', as the '_load' part is automatically added to the loader function's name before calling. That should probably be noted as part of the process as it's a little bit misleading as to how the wildcarding works.

aggregator_feed_load is an existing wildcard loader

girishmuraly - July 10, 2009 - 12:15

aggregator_feed_load is an existing wildcard loader!

See http://api.drupal.org/api/function/aggregator_feed_load/6 for definition.

For more information on existing wildcard loaders - http://drupal.org/node/209056.

hope that helps..

Adding MENU_LOCAL_TASK items for your node-type

ryan_courtnage - September 13, 2008 - 15:21

If you need to add a MENU_LOCAL_TASK for a specific node-type, use a loader:

<?php
$items
['node/%my_node_type/new_tab'] = array(
   
'title' => 'New Tab',
   
'page callback' => 'mycallback',
   
'page arguments' => array(1),
   
'access callback'   => TRUE,
   
'type' => MENU_LOCAL_TASK
)

...

function
my_node_type_load($arg) {
 
$node = node_load($arg);
  if(
$node->type == 'my_type')
    return
$node;
  return
FALSE;
}
?>

how to deal with if...else statements in menu_hook

judef - May 9, 2009 - 06:51

IN one of the module i am trying to port from 5x to 6x. the menu_hook has the following colde

if ($may_cache)
{

code goes here

}
else
{
different code goes here
}

My question is since Drupal6 menu_hook doesnot take $may_cache parameters how can i write a if..else statement if i remove $may_cache from above? any suggestions would be greatly appreciated.

Regards,
judef

judef

Did you see this portion of

bsenftner - January 5, 2010 - 22:54

Did you see this portion of the above text?

Additional Run Once Code

Non menu code that was placed in hook_menu !$may_cache so that it could be run during initialisation, should now be moved to hook_init. Previously we called hook_init twice, once early in the bootstrap process, second just after the bootstrap has finished. The first instance is now called boot instead of init.

 
 

Drupal is a registered trademark of Dries Buytaert.