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

/**
 * 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

/**
 * 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.

Comments

ehowland’s picture

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

justindodge’s picture

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.

girishmuraly’s picture

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..

ryan_courtnage’s picture

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

$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;
}
judef’s picture

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

bsenftner’s picture

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.