Add and Manipulate Pages with Menu System in Drupal

In this post, we cover different ways to add and manipulate pages with Menu System in Drupal.

Every page loaded in Drupal:

domainname.com/path/of/somekind

is transformed as following request:

domainname.com/index.php?q=path/of/somekind

This is what you will see if the ‘clean url’ setting is turned off.
In Drupal, every path is routed through single page – index.php. This is called front controller design pattern seen in many other web frameworks today. Drupal determines where to route the request by looking at the path(everything on the right of domainname.com)

Drupal takes the path and references an index it has that assigns every path to a function call. If it finds that path in the index than it calls that function and expects the function to tell Drupal what to do next. Most of the time it will provide some content to built into the final page, but it may as well redirect to go to another page or present error message like access denied

Every module determines which paths are assigned to which call back functions and all that data is stored in db table ‘menu_router’

Simple Menu Callback

In your module file MODULENAME.module, add the following:

function MODULENAME_menu() {
    $items['pages'] = array(
        'title' => 'Menu system examples',
        'description' => 'Menu system example that returns a string.',
        'page callback' => 'register_member_page',
        'access callback' => TRUE,
    );

    return $items;
}

function register_member_page() {
    $build = array(
        'header_text' => array(
            '#type' => 'markup',
            '#markup' => '<p class="lead">' . t('Membership Registration') . '</p>',
        ),
       'example_form' => drupal_get_form('collect_member_info_form'),
    );
    return $build;
}

This is implementation of hook hook_menu(). The hook_menu() expects array of call back function(i.e. register_member_page) along other info. Here the Url path defined is ‘/pages’. The ‘page callback'(i.e.register_member_page()) is the call back function routed to this url – ‘/pages’ that will tell what to do next. The ‘access callback’ specifies permissions.

We can supply content to page request in 2 ways:
1. Through string containing html and text
2. Through the build array containing content in a structred array that can be later manipulated by other modules before rendered

Next implement the page callback function ‘pages_string’ that supplies the content as simple string(Nr.1)

function pages_string() {
    $output = '
    <p>Pages can be returned as strings.</p>
    <p>Pages can be returned as <em>render arrays</em>.</p>';
    $output .= theme('item_list', array(
            'title' => 'Render arrays are better because...',
            'items' => array(
                'They allow content to be modified as an array.',
                'Arrays are a lot easier to modify than HTML.',
            ))
    );

    return $output;
}

Here we structure the output as a string and use theme(‘item_list’…) function to help construct a list order ed or unordered with heading if you wish.

Next, make sure the module is installed and go to domainname.com/pages to verity it works

How to Use Render Arrays and Tabs

When callback function returns build array instead a string, then other modules can manipulate the content by adding/removing or reorder the elements in content before its rendered

function pages_menu() {
    $items['pages'] = array(
        'title' => 'Menu system examples',
        'description' => 'Menu system example that returns a string.',
        'page callback' => 'pages_string',
        'access callback' => TRUE,
    );
    $items['pages/default'] = array(
        'title' => 'String',
        'type' => MENU_DEFAULT_LOCAL_TASK,
        'weight' => -10,
    );
    $items['pages/render-array'] = array(
        'title' => 'Render array',
        'description' => 'Menu system example using a render array.',
        'page callback' => 'pages_render_array',
        'access arguments' => array('access content'),
        'weight' => 2,
        'type' => MENU_LOCAL_TASK,
    );

    return $items;
}

Here we taken the pages content page and added two tabs. The page ‘pages/default’ is the default tab by specifying the type to default. Important to note, that the default tab doesn’t have the page callback specified, so it falls back to the parent callback function – ‘pages_string’

Another tab(‘pages/render-array’) is added with new call back function ‘pages_render_array’. This tab has access arguments. Unless boolean is specified, it expects function. If no specified as it is here, then it uses default callback ‘user_access’ and it expect permission to be passed which we do here with ‘content access’

The build render array looks as following:

function pages_render_array() {
    $build = array(
        'string_paragraph' => array(
            '#type' => 'markup',
            '#markup' => '<p>Pages can be returned as strings.</p>',
        ),
        'render_array_paragraph' => array(
            '#type' => 'markup',
            '#markup' => '<p>Pages can be returned as <em>render arrays</em>.</p>',
        ),
        'why_render_arrays' => array(
            '#items' => array('They allow content to be modified as an array.', 'Arrays are a lot easier to modify than HTML.'),
            '#title' => 'Render arrays are better because...',
            '#theme' => 'item_list',
        ),
    );
    return $build;
}

Here we specify to element to be html strings and then the third is unorder list. This is being passed to theme() function ‘item_list’ as specified

Drupal caches menu registry, so you have to tell Drupal to rebuild the registry and you can do by clearing the cache resulting to rebuild

Sub-Tabs

function pages_menu() {
...
    $items['pages/render-array'] = array(
        'title' => 'Render array',
        'description' => 'Menu system example using a render array.',
        'page callback' => 'pages_render_array',
        'access arguments' => array('access content'),
        'weight' => 2,
        'type' => MENU_LOCAL_TASK,
    );
    $items['pages/render-array/tab1'] = array(
        'type' => MENU_DEFAULT_LOCAL_TASK,
        'title' => 'Tab 1',
    );
    $items['pages/render-array/tab2'] = array(
        'title' => 'Tab 2',
        'description' => 'Demonstrating secondary tabs.',
        'page callback' => 'pages_render_array',
        'access callback' => TRUE,
        'type' => MENU_LOCAL_TASK,
    );

    return $items;
}

Here we define two new items – ‘pages/render-array/tab1’ and ‘pages/render-array/tab2’ both of which are subpages of tab ‘pages/render-array’, so its important that the sub-tab path corresponds to the pages path its sub-tab from

How to Add Page Without Menu Item

function pages_menu() {
...
        $items['pages/callback'] = array(
        'title' => 'Example of a callback type',
        'page callback' => 'pages_render_array',
        'access callback' => TRUE,
        'type' => MENU_CALLBACK,
    );
...

Here we define separate page by specifying the type to be ‘MENU_CALLBACK’ instead ‘MENU_LOCAL_CALLBACK’. After clearing cash, the new page is displayed at domain.com/pages/callback end point

How to Pass Variables Through the Path

function pages_menu() {
...
 $items['pages/argument'] = array(
        'title' => 'Argument',
        'description' => 'Menu system example using an argument.',
        'page callback' => 'pages_argument',
        'access arguments' => array('access content'),
    );
}

function pages_argument($arg1) {
    $build['argument_paragraph'] = array(
        '#type' => 'markup',
        '#markup' => '<p>' . t('The argument passed was @arg1.', array('@arg1' => $arg1)) . '</p>',
    );

    return $build;
}

The only difference is to change the callback function to accept arguments. Here the callback function ‘pages_argument’ takes an arguments. So, with path domain.com/pages/argument/hello’, the argument ‘hello’ is going to be passed to the callback function ‘pages_argument’. For more arguments you just add them to the callback function parameter. Also can use the Drupals args() function

How To Use Placeholders to Pass Arguments in Middle of the Path

When you know the beginning and the end of the path but not the middle (i.e. node/*/edit)

function pages_menu() {
...
 $items['pages/%/placeholder'] = array(
        'title' => 'Placeholder',
        'description' => 'Menu system example using a placeholder.',
        'page callback' => 'pages_argument',
        'page arguments' => array(1),
        'access arguments' => array('access content'),
    );
}

There are two places to register the placeholder in hook_menu(). In the path, by specifying ‘%’ and ‘page arguments’ attributed as highlighted above. The number 1 specifies the location(starting from 0) of the path variable of placeholder. Afterwards, this variable is passed to the callback function

How To Create Dynamic Title With Title Callback

function pages_menu() {
...
  $items['pages/title/%'] = array(
        'description' => 'Example of a dynamic title.',
        'page callback' => 'pages_render_array',
        'access callback' => TRUE,
        'title callback' => 'pages_title_callback',
        'title arguments' => array(2),
    );
...
}

function pages_title_callback($arg) {
    return 'There is an argument in this title: ' . $arg;
}

Here, we specify the title callback and pass an argument. Afterward, we declare the callback that returns a string and is rendered as title

How To Modify Page Output with hook_page_alter()

How do adjust menu items that have been defined by other modules
How do we change the value returned by callback function defined by other modules

The idea of hook_page_alter() is that before content is rendered, modules have the last chance to manipulate the rendered page content.

function THEME_page_alter(&$page) {
    $page['content']['system_main']['why_render_arrays']['#weight'] = -10;
}

Here we reorder the array so that element item list named ‘why_render_arrays’ is moved up before other elements

How To Modify Menu Items With hook_menu_alter()

Instead modifying the render array, we modify the callback function all together. We will replace one page already exist with another we defined
Anything that is defined in hook_menu will be passed through the hook_menu_alter where it can be altered one more time

function pages_menu_alter(&$items) {
    $items['admin/content']['title'] = 'The Goods';
    $items['admin/content']['page callback'] = 'pages_render_array';
}

Here we override the admin page with our own defined page as declared in callback ‘pages_render_array’

How Define Path To Be Admin

Perhaps, you would like your new custom path to be part of Admin path whether its because you like to have the page in overlay or take advantage of the admin permissions, then:

function MODULE-NAME_admin_paths() {
    $paths = array(
        'path/*/edit' => TRUE,
    );
    return $paths;
}

here all the path of pattern – “‘path/*/edit” is going to be admin path with all the admin access rights, overlay functionality,etc.

How Use Include File To Improve Performance

On every page load, every .module file got loaded for every module currently installed. If we can make the code shorter in those .module files then we can improve the performance. One way doing it is by separating out the .module code into separate include files and then use special parameter in the hook_menu() item declaration goes in fetches that include file when needed. The idea, all of the code is not needed in the .module file at the same time.

function pages_menu() {
...
$items['pages/external'] = array(
        'title' => 'External file',
        'description' => 'Example of using an include for a page callback.',
        'page callback' => 'pages_external',
        'access callback' => TRUE,
        'file' => 'pages.external.inc',
        'file path' => drupal_get_path('module', 'NAMEOFMODULE'),
    );
...
}

Here, we specify the callback function ‘pages_external’ but the function is not in the .modules file. it is in the file ‘pages.external.inc’ that we specify to load in the highlighted lines. It will only include this file, when requesting ‘pages/external’

Leave a Reply

Your email address will not be published. Required fields are marked *