Menu system

by Gisle Hannemyr

This chapter introduces the Drupal 7 menu system and describes how to to make forms and pages with content created using code accessible through menu routes.

Table of contents

Introduction

In this chapter will introduce the Drupal 7 menu system. The menu system facilitates site navigation by means of paths.

The Drupal 7 menu system drives both site navigation from a user perspective and the call­back system that Drupal uses to respond to URLs passed from the browser.

The example in the previous chapter demonstrated very simple use of Drupal's menu system by means of hook_menu(). In this chapter, we shall dig deeper into the menu system.

tipThe convention is to place hook_menu() and any access functions in the project's .module file, while all the callbacks and their handlers go into a file with a name eding with .admin.inc. To get this file loaded when it is needed, use the file attribute in the array contructed by hook_menu() (see examples below). There is no need to list it in the project's .info-file.

You may put items into the existing menus on the website by means of the administrative GUI. To do so, navigate to Structure » Menus and click “add link” for the menu you want to modify.

The menu system in Drupal 7 follows a simple hierarchy defined by paths. Implementations of hook_menu() define menu items and assign them to paths (which should be unique). The menu system aggregates these items and determines the menu hierarchy from the paths.

For example, if the paths defined were: a, a/b, e, a/b/c/d, f/g, a/b/h, the menu system would form the structure:

Modules that wants to display page content that can be addressed by a URL in Drupal 7 must register a path for it. Registering a path means defining a menu router entry for the page by implementing hook_menu().

To define your menu router entry, first you will need to choose a path. All paths originate at your site's base URL.

Drupal 7 stores menu router items in a database table: {menu_router}. It does this because looking this up in the database query is faster than searching through all implementations of hook_menu() to find a match for the path.

To create a menu router item for a custom page, hook_menu() should return an associative array defining the item.

Menu types

Your menu array may specify a menu type. The following types exist:

Source: Menu item types.

The MENU_NORMAL_ITEM will by default show up in the navigation menu, to add it to another manu, set it explictly:

function mymodule_menu() {
  $items['helloworld'] = array(
    'title' => 'Hello world',
    'page callback' => 'mymodule_custom',
    'menu_name' => 'main-menu',
    'access callback' => TRUE, 
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

Menu for a project

In addition to the menus that can be accessed through Structure » Menus or programatically added to a named menu, Drupal 7 projects can set up menus for the administrative pages for a project.

To get a configuration menu linked to from the site's modules overview page, you have to have its path assigned to the attribute configure in the project's .info-file. Example:

configure = admin/config/people/example-menu

A project may create several menus, but there should only be one configuation menu.

The most common type of menu gives the function drupal_get_form() as the callback, and gives the name of the function to implement the business logic for the form in the first page argument.

Below is an example of a menu to access an administrative menu with two tabs. To add more tabs, just reuse the boilerplate for the last item. If no tabs are required, only the first item (MENU_NORMAL_ITEM) is required.

/**
 * Implements hook_menu().
 */
function mymodule_menu() {
  $items = array();
  $items['admin/config/people/example-menu'] = array(
    'title' => 'Example',
    'description' => 'This description shows up on the configuration page.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('mymodule_tab1'),
    'access callback' => TRUE,
    'file' => 'mymodule.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/config/people/example-menu/not-shown] = array(
    'title' => 'First tab',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => 0,
  );
  $items['admin/config/people/example-menu/second'] = array(
    'title' => 'Second tab',
    // No description required.
    'page callback' => 'drupal_get_form',
    'page arguments' => array('mymodule_tab2'),
    'access callback' => TRUE,
    'file' => 'mymodule.admin.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => 1,
  );
  return $items;
}

The MENU_DEFAULT_LOCAL_TASK is the default tab shown at the path pointed to by MENU_NORMAL_ITEM. The path for the MENU_DEFAULT_LOCAL_TASK must be different from the path for MENU_NORMAL_ITEM, but it is not displayed to the user.

Below are the boilerplate callback functions for the above menu.

/*
 * Menu callback: Provide first tab page for MyModule.
 *
 * @return array
 *   Form.
 */
function mymodule_tab1() {
  $form['mymodule_msg'] = array(
    '#markup' => t('This is the first tab'),
  );
  return $form;
}

/*
 * Menu callback: Provide second tab page for MyModule.
 *
 * @return array
 *   Form.
 */
function mymodule_tab2() {
  $form['mymodule_msg'] = array(
    '#markup' => t('This is the second tab'),
  );
  return $form;
}

Custom callback

Instead of using the predefined callback function drupal_get_form(), you may write a custom callback function. You will typically do this if the menu does not link to a form, but to content you want to create programmatically, rather than by using the GUI to create a node.

To pass arguments to the callback function, use page arguments.

The page arguments may be strings:

function mymodule_menu() {
  $items['helloworld'] = array(
    'title' => 'Hello world',
    'page callback' => 'mymodule_custom',
    'page arguments' => array('wonderful', 'crazy'),
    'access callback' => TRUE, 
    'file' => 'mymodule.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

A callback function that outputs the aguments:

function mymodule_custom($arg1, $arg2) {
  $content['raw_markup'] = array(
    '#type' => 'markup',
    '#markup' => '<p>Hello, ' . $arg1 . '! It is a ' . $arg2 . '  world.</p>',
  );
  return $content;
}

To pass the arguments as part of the path, use numeric page arguments:

function mymodule_menu() {
  $items['helloworld/%/%'] = array(
    'title' => 'Hello world',
    'page callback' => 'mymodule_custom',
    'page arguments' => array(1,2),
    'access callback' => TRUE, 
    'file' => 'mymodule.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

Given the same callback function, the following path will produce an identical output as the previous example.

/helloworld/wonderful/crazy

Access control

In the examples above, access callback is set to the constant TRUE, giving everyone access.

This is not recommended, as it would provide access to users that don't even have the access content permission granted. Instead, make sure access callback is a function (by default, it is: user_access()). When it is a function you should also provide access arguments. This is an array containing the arguments passed to the access callback function. Example:

function mymodule_menu() {
  $items['helloworld'] = array(
    'title' => 'Hello world',
    'page callback' => 'mymodule_custom',
    //'access callback' => 'user_access',
    'access arguments' => array('access content'), 
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

As with other menu callbacks, the arguments must be a string or a number. If it is a number, the value will be replaced with a value taken from the path. If it is a string, the value will be the name of the permission to check for (here: access content).

The access callback function is a the name of a any function that is called to verify if the user has access to the menu callback. It should return either TRUE (the user is granted access) or FALSE (the user is denied access).

tipIf you don't want to specify permissions that are specific to your module, you may want to consider reusing the following core permissions (hopefully the intended use is obvious): “access content” (can be given to all roles), “administer site configuration” (give to trusted roles only), “administer users” (give to trusted roles only).

Final word

[TBA]


Last update: 2019-04-08 [gh].