0

I'm trying to create a tabbed menu system with hook_menu() following the hook documentation and Clive's answer. I am using the following code.

/**
 * Implements hook_menu().
 */
function newforms_menu() {
  $items = array();
  $items['admin/config/content/newforms'] = array(
    'title' => 'NewForms',
    'description' => 'Configure web forms via drag and drop.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('newforms_administration_settings'),
    'access arguments' => array('administer newforms module'),
    'file' => 'newforms.admin.inc',
  );

  $items['admin/config/content/newforms/list'] = array(
    'title' => 'Forms',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );

  $items['admin/config/content/newforms/components'] = array(
    'title' => 'Components',
    'type' => MENU_LOCAL_TASK,
    'page arguments' => array('newforms_components_list'),
  );

  return $items;
}

I would expect this to produce two tabs, labeled 'Forms' and 'Components', but it does not. I see no tabs. On admin/config/content/newforms/list, I get the same page as admin/config/content/newforms; on admin/config/content/newforms/components, I get "You are not authorized to access this page."

I've cleared cache and ran cron, disabled the module, uninstalled it, reinstalled it, cleared cache, and ran cron; I get the same results.
Both callback argument form functions are defined in newforms.admin.inc.

Do you have any idea as to what is going on here?

1

What happens is documented in a comment for _menu_router_build(), which the function that builds the menu routers from the hook_menu() implementations done from modules.

    // If an access callback is not found for a default local task we use
    // the callback from the parent, since we expect them to be identical.
    // In all other cases, the access parameters must be specified.

Your fist menu is a MENU_DEFAULT_LOCAL_TASK, so you can avoid specifying the access arguments. The other menu is a MENU_LOCAL_TASK, and you need to specify the access arguments; failing to do so will cause Drupal to consider the second tab not accessible. That is the reason you don't see any tab: Drupal sees one accessible (i.e. visible) tab, and it doesn't show any tab because Drupal shows tabs only when there are more than one visible tab.

1

So this works:

/**
 * Implements hook_menu().
 */
function newforms_menu() {
  $items = array();
  $items['admin/config/content/newforms'] = array(
    'title' => 'NewForms',
    'description' => 'Configure web forms via drag and drop.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('newforms_administration_settings'),
    'access arguments' => array('administer newforms module'),
    'file' => 'newforms.admin.inc',
  );

  $items['admin/config/content/newforms/list'] = array(
    'title' => 'Forms',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );

  $items['admin/config/content/newforms/components'] = array(
    'title' => 'Components',
    'type' => MENU_LOCAL_TASK,
    'page arguments' => array('newforms_components_list'),
    'access arguments' => array('administer newforms module'),
  );

  return $items;
}

I think there were two things happening. I have two theories. One was that Drupal essentially saw two identical tabs and said "this is dumb I'm not doing this" and didn't render the components tab. The other theory is that as soon as Drupal saw a different page argument for the second tab (components), it demanded an access arguments specification. I'm not sure which is true, if both are true, or if both are wrong, but this works.

The moral of the story is that things in hook_menu don't inherit the way you expect them to sometimes.

EDIT:

This is straight from the hook_menu documentation.

// Make "Foo settings" appear on the admin Config page
$items['admin/config/system/foo'] = array(
  'title' => 'Foo settings',
  'type' => MENU_NORMAL_ITEM,
  // Page callback, etc. need to be added here.
);
// Make "Tab 1" the main tab on the "Foo settings" page
$items['admin/config/system/foo/tab1'] = array(
  'title' => 'Tab 1',
  'type' => MENU_DEFAULT_LOCAL_TASK,
  // Access callback, page callback, and theme callback will be inherited
  // from 'admin/config/system/foo', if not specified here to override.
);
// Make an additional tab called "Tab 2" on "Foo settings"
$items['admin/config/system/foo/tab2'] = array(
  'title' => 'Tab 2',
  'type' => MENU_LOCAL_TASK,
  // Page callback and theme callback will be inherited from
  // 'admin/config/system/foo', if not specified here to override.
  // Need to add access callback or access arguments.
);

The second to last line is what tripped me up. For every MENU_LOCAL_TASK you must add an access callback/access argument. Mystery solved.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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