Note: Terms used in this article.

  • argument: as in arguments of a php function. And
  • element means an argument of the path. Example: /node/224170/edit - 224170 is an element.

The Basics

When a wildcard loader is utilized in a menu path, the value of the element at the position of the loader definition is passed to the wildcard loader function as the first argument.

Example:

$items['node/%node/revisions'] = array(
  [...]
);

When a % symbol is used in a menu element (see example), the text that follows will be used to build the name of wildcard loader function.

In the example above, "%node" tells drupal to pass the value of the second element to the function node_load(). _load is always added by Drupal to construct the wildcard loader.

So, if you were to go to node/1337/revisions in your browser, then node_load(1337) would be called.

Multiple Wildcard Loader Arguments

Drupal will always execute the loader function with the first argument of the loader being the path element where the loader is defined. But what if we want to add custom arguments to the loader function?

To get Drupal to include another element of the path, such as a second wildcard, we need to use the “load arguments” menu item property.

Remember: the first argument is always going to be the value where the loader is defined, any arguments in "load arguments" will be 2nd, 3rd values etc. A better way to think of "load argument" is extra load arguments because the first argument is always assumed.

Example:

$items['node/%node/revisions/%/view'] = array(
'title' => 'Revisions',
'load arguments' => array(3), // HERE
'page callback' => 'drupal_get_form',
'page arguments' => array('node_revision_revert_confirm', 1),
'type' => MENU_CALLBACK,
);

The "load arguments" in the above example defines additional arguments to pass to the load function. By default, the load function (node_load() in this case) will only be passed part 1 (the text in position "%node" in this case) of the path.

If we want to pass a node revision to node_load() which is its second argument, we can define "load arguments" with additional arguments in an array. Positive integers in this array are handled specially; they're replaced with the part of the path in the corresponding position. In this example, 3 is replaced with part 3 (with counting beginning at zero) of the actual path when a user browses to the page. So, if we navigate to node/1337/revisions/42/view then node_load(1337,42) would be called.

As usual, the returned value of the loader function can then be used by the “access callback” or a “page callback” by including the element's index as an “access argument” or “page argument”. Using the example above, the returned value of node_load will be passed to the page callback like this:

drupal_get_form('node_revision_revert_confirm', $node_load_returned_value_here);

Note: When using drupal_get_form as the page callback, the first variable passed to the function is $form_state. (e.g. function node_revision_revert_confirm($form_state, $node_revision ). So when you send arguments they will be available as second, third ... and so on.

Special Wildcard Loader Arguments

Using integers to get the path element substitute is very useful, but there are also ways to get even more information.

%map and %index

  • %map: All elements of the path are converted to a keyed array. This is also a variable reference
  • %index: The element of the path the wildcard loader is defined in.

Example:

 $items['user/%user_category/edit'] = array(
'title' => 'Edit',
'page callback' => 'user_edit',
'page arguments' => array(1),
'access callback' => 'user_edit_access',
'access arguments' => array(1),
'type' => MENU_LOCAL_TASK,
'load arguments' => array('%map', '%index'),
'file' => 'user.pages.inc',
); 

If we navigated to user/55/edit then user_category_load($uid, $map_array, $index) would be called.

The first argument of the loader function would be the integer 55.

The second argument of the loader would be

array(
  [0] => "user",
  [1] => 55,
  [2] => "edit",
)

The third argument of the loader would be the integer 1, because that is the position of the path that the loader was defined in.

More Information

If a wildcard loader function returns FALSE it would be the equivalent of page not found.

If you want to look into how all this works, go to the _menu_load_objects function in includes/menu.inc

Comments

j0hn-smith’s picture

My testing shows that _load functions can't be in inc files, if they are the path doesn't work.

_load functions must be in .module files.

dpi’s picture

Wildcard loader functions must be available when the menu tree is being built. A safe place to put them would be in your modules' .module file, because all enabled .module files are called by Drupal automatically. To keep things tidy, you could do an include in the root scope of your .module file to an .inc file which contains a loader.

GuyPaddock’s picture

I tried a wildcard path like "admin/content/node-type/%mymodule_node_type/mymodule", and defined a custom node type loader "mymodule_node_type", but it never caused a tab to be rendered on the page with the path "admin/content/node-type/story".

Conversely, using the foreach-loop approach of adding a path for each node type ("admin/content/node-type/story/mymodule" in this case) worked fine.

As a result, I don't think wildcard replacement works for local tasks...

emichan’s picture

I'm not sure exactly how your menu and load function was set up, but I have used wildcard loaders with local tasks and it does work. For example, I wrote a custom module that keeps track of trips, here is the relevant excerpt from my hook_menu function:

$items['admin/content/trips/%trip'] = array(
      'title' => 'Trip',
      'page callback' => 'trips_view_trip',
      'page arguments' => array(3),
      'access arguments' => array('administer trips'),
   );

   $items['admin/content/trips/%trip/view'] = array(
      'title' => 'View Trip',
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -10,
   );

   $items['admin/content/trips/%trip/edit'] = array(
      'title' => 'Edit Trip',
      'page callback' => 'drupal_get_form',
      'page arguments' => array('trips_form',3),
      'access arguments' => array('administer trips'),
      'type' => MENU_LOCAL_TASK,
      'weight' => -5,
   );

   $items['admin/content/trips/%trip/delete'] = array(
       'title' => 'Delete Trip',
       'page callback' => 'drupal_get_form',
       'page arguments' => array('trips_delete_form',3),
       'access arguments'  => array('administer trips'),
       'type' => MENU_LOCAL_TASK,
   );

My load function was called 'trip_load' and returned an object from the database.

srjosh’s picture

I think that your custom node type loader would need to be "mymodule_node_type_load"...

Mixologic’s picture

http://drupal.org/node/209056 - says the function to be defined is called your_loader_name_load()

so %typo is definitely typo_load().

So your loaders should ideally be namespaced. If you put %thing in your hook menu, somebody else will too.

so your *loader* should say something like %mymodule_thing and then your function is mymodule_thing_load();

dpi’s picture

Loaders do not inherit any naming from modules or node types. They are completely custom.

xjm’s picture

Gribnif: a better way to think of "load argument" is extra load arguments because the first argument is always assumed.

What? Is Gribnif someone's name/nick/handle left there by accident, or some esoteric new slang? In any case, it should be removed or clarified...

splash112’s picture

Nice, that Wildcard Loader Arguments function.
Wrote my own loader function which works like a charm. Very safe indeed!

Sadly, it also messes up my ability to use an element in the Title of the pages.

The loader function loads an array, which I cannot use anymore in the title? Or would one always need to write a custom title callback?

node/%node
node/11

makes it impossible to use that 11 straight in the title.

soegaraedy’s picture

Please remember to put the loader function in the same file where your menu function is in, it usually is the .module file. In my case, my loader function wouldnt fire when i put it in the .inc file.