I use logic like this to place a tab on each user's profile page so users can view analytics from the usage of a WebApp:

  // maps a url to a sub-page of user's profile, url to user's Analytics-Management form
  $items['user/%user/ClientCompanyName'] = array(
    'title'            => 'ClientCompanyName',
    'description'      => 'Manage your ClientCompanyName services.',
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('_ClientCompanyName_mgmt_form'),
    'access callback'  => 'user_access',
    'access arguments' => array( 'access ClientCompanyName services' ), 
    'type' => MENU_LOCAL_TASK,

This works great for each user, but I do not want one user to be able to go to another user's profile and hit their link to get to that other user's private analytics data.

Currently, if a user tries to visit another user's analytics management page, they see an access denied page.

But I would like them never to even be given the link to that page. I tried writing my own 'access callback', but I can't figure out how to pass in the '%user' portion of the intended url so I can test that against the logged in user...

I like how one can use '%' symbols in the url, and then 'page arguments' can be used to reference those arguments in the url. Is it possible to do something similar with the 'access arguments'?

Essentially, I'm trying to achieve the same thing as the behavior of the 'edit' tab on a user's profile: only if the user is on their own profile page will that 'edit' tab appear on the profile. How do I get the same effect for my WebApp analytics page?

(I guess I could make my analytics page a MENU_LOCAL_TASK of the profile edit form... but I'd still like to know how to create a conditional MENU_LOCAL_TASK whose conditional is more then simply an user_access() permissions test.)

2 Answers 2


Instead of user_access, define your own access callback function and then pass the user object to that.

So 'access callback' => 'yourmodule_analytics_access' and 'access arguments' => array(1).

In that function, compare the user id and check the permission.

function yourmodule_analytics_access($account) {
  global $user;
  return $user->uid == $account->uid && user_access('access ClientCompanyName services');
  • Using "'access arguments' => array(1)" will pass "1" to my access callback, not the 1'th argument from the url. I tried what you suggest. The issue seems to be that I can't get information from the url as a parameter to my custom access callback. Commented Mar 18, 2011 at 1:51
  • It works. See api.worldempire.ch/api/privatemsg/privatemsg.module/function/… for some examples. Maybe you didn't rebuild the menu after changing it?
    – Berdir
    Commented Mar 18, 2011 at 2:15
  • Wow. You are right. At first I was trying "array('%user')" and not 'array(1)'... and when I tried 'array(1)' I was not expecting the entire user object, I was expecting a string equal to the uid of the profile being visited. I think I'm going to switch to your suggestion. Commented Mar 18, 2011 at 2:29
  • 2
    @bsenftner using an integer as part of an arguments array means that numbered argument will be passed. If you want to pass a literal 1 you need to put "1" into the arguments array. The reason you get a full user object is because argument 1 is %user which calls user_load($arg) and passes that into your function. In general, using a %foo parameter in your URL will cause the result of foo_load($arg) to be passed to your function. Commented Mar 18, 2011 at 3:48
  • @Matthew Scharley: thanks for the additional EXTREMELY USEFUL information. As far as I know, that's not documented anywhere, and very, very valuable. Commented Mar 20, 2011 at 15:22

I ended up using a custom access callback like this:

// this makes the analytics user profile tab's MENU_LOCAL_TAB conditional:
function _cex_mgmt_access($accessAllowed) {
  global $user;
  if (arg(1) != $user->uid)
    return false;
  return user_access($accessAllowed);

Where the 'access callback' parameter in the MENU_LOCAL_TASK definition is set to '_cex_mgmt_access' rather than 'user_access'.

I'm a bit uncertain about this method. It depends upon the menu module's logic that handles the MENU_LOCAL_TASK definition to be processed each time a user travels to any profile page. Doesn't that information get cached (if caching is turned on)?

  • Actually, see my "wow" comment above. I switched to that answer after posting my first solution. Commented Mar 18, 2011 at 2:30
  • To answer your question, no it doesn't and can't get cached. But it is a fast check (the user object is already loaded and re-used), so don't worry about that.
    – Berdir
    Commented Mar 18, 2011 at 8:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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