The second function 'example_user_access()' (two listed below) implements the 'hook_user_access()' function. The line:

$account = array_shift($arguments);

I understand is set to '%user'. So if we were at the url 'user/jmm42/content', then it should be:

$account = jmm42.

However, the code implies the value 'jmm42' is keyed by 'uid', where:

$account->uid = jmm42 // (#1)

- - -

function example_menu() {
  $items['user/%user/content'] = array(
    'title' => 'Content creation',
    'page callback' => 'example_user_page',
    'page arguments' => array(1),
    'access callback' => 'example_user_access',
    'access arguments' => array(1, 'view content creation permissions', 'administer users'),
    'type' => MENU_LOCAL_TASK,
  return $items;
function example_user_access() {
  global $user;
  $arguments = func_get_args();
  $account = array_shite($arguments);
  if ($account->uid != $user->uid) {  // (#1)
    return FALSE;
  foreach ($arguments as $permission) {
    if(!user_access($permission)) {
      return FALSE;
  return TRUE;


jmm42’s picture

The example (provided below), takes %node and assigns it to the '$node->type'. Where is this key 'type' coming from? So, if we were at the url 'node/abc/test'

the code implies the value 'abc' is keyed by 'type', namely:

$node->type == 'abc' ... // (#2)

Can someone direct me to what file / function in core that makes these assignments (#1), (#2)?

- - -

function addtabexample_menu() {
  $items = array();

  $items['node/%node/test'] = array(
    'title' => 'Test',
    'page callback' => 'handle_test',
    'page arguments' => array('node', 1),
    'access callback' => 'addtabexample_access_callback',
    'access arguments' => array(1), 
    'type' => MENU_LOCAL_TASK,
    'weight' => 100,

  return $items;

function addtabexample_access_callback($node) {
  return $node->type == 'abc' && user_access('access content');  // (#2)
goofus’s picture

The detailed answer nevets has provided is exactly correct. My suggestion is in addition to that correct answer.

I've noticed you've posted a lot of really good questions (in this forum). Another way of getting your questions answered is to run Drupal through a standard source code debugger. For php. I use xdebug along with an IDE. The IDE provides the debugger with an easy to use, GUI front end. There are many free IDE's to choose from. I use Netbeans for an IDE -- php version ( XDebug as a debugger ( There is a wiki for the setup here: Again there are many other fine IDE's. Netbeans just happens to be the one I am using for PHP projects (like Drupal).

If you run Drupal through a debugger, you can step through the code processing and inspect the data structures. For me, it's the best way to make full use of "open source" :) Thus, I highly recommend checking it out. Specially with someone like your self who has a healthy curiosity !! (meant as a complement :) ).

Good Luck :)

nevets’s picture

It is a combination of the path (user/%user/content) and the page arguments (array(1)).

A number (1) in the array of page arguments says to use that part of the path (0 would be 'user', 1 is '%user' and 2 is 'content'). The %user (as well as %node) tells Drupal to call user_load (node_load) with the value in the path (a user id) and the value returned is what is passed the callback.

jmm42’s picture

Ah, yes -- had forgotten about that. What if the path looked like: user/%/content -- would a loader function get called, or would we just simply pass that magic value '%' as a parameter to respective segments of the hook_menu()?

jmm42’s picture

In, this case, no loader function is called for the second portion of the url, namely '%':


Rather, only the access argument is substituted with the '%' value. For more details:

- - -

For more information:

'menu_router_build()' invokes 'hook_menu()' defined within the ''. This function contains '_menu_router_build()' which invokes each of the load functions (in our case 'user_load()'). The load function (and more generally, the menu router items) is cached within the following line of 'menu_router_build()':