Problem/Motivation

In function _menu_check_access() of includes/menu.inc, the documentation asserts:

$item['access'] becomes TRUE if the item is accessible, FALSE otherwise.

However, if the $item['access_callback'] function does not exist, the 'access' property will not be set as advertised. This results in subtle and hard-to-troubleshoot bugs in apparently unrelated areas of code.

Proposed resolution

The patch in #45 logs a warning and sets $item['access'] to FALSE when the callback function does not exist.

The proposed patch is a D7-only solution. In D8, the function_exists() check was eliminated by #1059884-22: Drop function_exists for 'callback' functions.

Remaining tasks

The patch in #45 needs to be reviewed.

User interface changes

None.

API changes

None.

Original report by pillarsdotnet

Got some "undefined index" errors on a fresh install, and fixed them as follows:

diff --git includes/menu.inc includes/menu.inc
index 47d4941..c9fd51e 100644
--- includes/menu.inc
+++ includes/menu.inc
@@ -891,7 +891,7 @@ function _menu_link_translate(&$item, $translate = FALSE) {
       _menu_check_access($item, $map);
     }
     // For performance, don't localize a link the user can't access.
-    if ($item['access']) {
+    if (!empty($item['access'])) {
       _menu_item_localize($item, $map, TRUE);
     }
   }
@@ -1424,7 +1424,7 @@ function _menu_tree_check_access(&$tree) {
   foreach ($tree as $key => $v) {
     $item = &$tree[$key]['link'];
     _menu_link_translate($item);
-    if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) {
+    if (!empty($item['access']) || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) {
       if ($tree[$key]['below']) {
         _menu_tree_check_access($tree[$key]['below']);
       }
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Damien Tournoud’s picture

Priority: Minor » Normal
Status: Needs review » Postponed (maintainer needs more info)

We need more information here. At this point 'access' must be defined. The fact that it is not defined is a bug that needs investigating, not hiding under the carpet :)

Can you come up with reproduction steps that are as detailled as possible?

pillarsdotnet’s picture

@Damien -- Thanks. I never know whether these variable keys are supposed to be defined, or whether the original programmer had E_NOTICE turned off.

Working on reproducing the issue, starting over with a blank database and reinstalling module-by-module, with the following code inserted:

    if (!isset($item['access'])) { error_log("access array key missing from item " . print_r($item,1) . ", backtrace:" . print_r(debug_backtrace(0),1)); }

It's entirely possible that the original error happened during my abortive attempt to do a D6--D7 upgrade. If I can't reproduce on a clean install I'll close this issue, and thanks again for your input.

pillarsdotnet’s picture

Project: Drupal core » Administration menu
Version: 7.x-dev » 7.x-3.x-dev
Component: menu system » Code
Status: Postponed (maintainer needs more info) » Active

Okay, the error is definitely triggered by the installation of admin_menu. Moving to that project.

pillarsdotnet’s picture

Unfortunately, starting from a clean install and then adding admin_menu and admin_menu_toolbar, the problem doesn't trigger. So it's a combination of two or more modules that don't work together (sigh).

Still narrowing it down...

pillarsdotnet’s picture

Okay, here's the minimal steps to reproduce:

  1. Install Drupal-7 HEAD from CVS with the "Minimal" profile.
  2. Patch includes/menu.inc as follows:
    Index: includes/menu.inc
    ===================================================================
    RCS file: /cvs/drupal/drupal/includes/menu.inc,v
    retrieving revision 1.427
    diff -u -r1.427 menu.inc
    --- includes/menu.inc   2 Dec 2010 20:18:23 -0000       1.427
    +++ includes/menu.inc   11 Dec 2010 23:27:01 -0000
    @@ -891,6 +891,7 @@
           _menu_check_access($item, $map);
         }
         // For performance, don't localize a link the user can't access.
    +    if (!isset($item['access'])) { error_log("access array key missing from item " . print_r($item,1) . ", backtrace:" . print_r(debug_backtrace(0),1)); }
         if ($item['access']) {
           _menu_item_localize($item, $map, TRUE);
         }
    @@ -1424,6 +1425,7 @@
       foreach ($tree as $key => $v) {
         $item = &$tree[$key]['link'];
         _menu_link_translate($item);
    +    if (!isset($item['access'])) { error_log("access array key missing from item " . print_r($item,1) . ", backtrace:" . print_r(debug_backtrace(0),1)); }
         if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) {
           if ($tree[$key]['below']) {
             _menu_tree_check_access($tree[$key]['below']);
    
  3. Enable the "Field UI"module.
  4. Install the "Administration menu" module from CVS and enable it.

Attached:

  • database dump immediately before enabling "Administration menu" module.
  • error_log output from above debugging code (truncated to max upload filesize).
  • database dump immedately after enabling "Administration menu" module.
pillarsdotnet’s picture

Weird. Now I can't duplicate...

Wonder if the problem was due to Google Chrome's "autocomplete prediction service" which I have subsequently turned off.

EDIT: Nope. Happened again as soon as I enabled some more modules... (sigh) Still debugging...

pillarsdotnet’s picture

Project: Administration menu » Drupal core
Version: 7.x-3.x-dev » 7.x-dev
Component: Code » field system

Okay, I'm moving this back to Drupal core because the error seems to be in the field_ui module.

Here's the sequence of events:

  1. in function _menu_translate() of includes/menu.inc, the documentation guarantees that $item['access'] will be either TRUE or FALSE when the function exits. To fulfill this guarantee, it calls
      _menu_check_access($router_item, $map);
    

    near the end of the function.

  2. in function _menu_check_access() of includes/menu.inc, the following code is executed:
          $item['access'] = call_user_func_array($callback, $arguments);
    

    with $callback = '_field_ui_view_mode_menu_access' and $arguments=array('user', 'user', 'full', 'user_access', 'administer users')

  3. In _field_ui_view_mode_menu_access() of modules/field_ui/field_ui.module, the following code:
    function _field_ui_view_mode_menu_access($entity_type, $bundle, $view_mode, $access_callback) { 
      // First, determine visibility according to the 'use custom display' 
      // setting for the view mode. 
      $bundle = field_extract_bundle($entity_type, $bundle); 
      $view_mode_settings = field_view_mode_settings($entity_type, $bundle); 
      $visibility = ($view_mode == 'default') || !empty($view_mode_settings[$view_mode]['custom_settings']); 
      
      // Then, determine access according to the $access parameter. This duplicates 
      // part of _menu_check_access(). 
      if ($visibility) {
    # ... (intervening code omitted for brevity) ...
      }
    }
    

    results in $visibility being FALSE, so the entire body of the function is skipped and no value is returned at all.

I believe that this is either a bug in _menu_translate (for relying on access callbacks to fulfill its contract) or in field_ui (for failing to set access in _field_ui_view_mode_menu_access).

pillarsdotnet’s picture

Status: Active » Needs review
FileSize
429 bytes

One-line patch.

pillarsdotnet’s picture

Title: Undefined index 'access' in includes/menu.inc » _field_ui_view_mode_menu_access() exits without returning a value.

Better title.

geekgirlweb’s picture

Even after patching I am still having the same error message displayed.

Notice: Undefined index: access in _menu_translate() (line 760 of /home/username/public_html/includes/menu.inc).
Notice: Undefined index: access in _menu_translate() (line 760 of /home/username/public_html/includes/menu.inc).
Notice: Undefined index: access in menu_local_tasks() (line 1874 of /home/username/public_html/includes/menu.inc).
Notice: Undefined index: access in menu_local_tasks() (line 1874 of /home/username/public_html/includes/menu.inc).

Correction, even after applying both patches:
http://drupal.org/files/issues/modules-field_ui.module.patch
http://drupal.org/files/issues/menu.inc-Undefined-index--access--in--ite...

pillarsdotnet’s picture

@gamerxgirl -- probably there is another helper function being called that also fails to return a value. IMHO, _menu_translate should issue a warning when its helper function fails to return a value.

patch attached.

EDIT: Ignore http://drupal.org/files/issues/menu.inc-Undefined-index--access--in--ite...
It was checking in the wrong place.

pillarsdotnet’s picture

Title: _field_ui_view_mode_menu_access() exits without returning a value. » Fix _field_ui_view_mode_menu_access() and _menu_translate() functions

Fresh title

geekgirlweb’s picture

I undid the http://drupal.org/files/issues/menu.inc-Undefined-index--access--in--ite... patch and used the one you supplied. I haven't spotted the error messages yet, so I think that might have done the trick. If they do pop-up again I will let you know. Thanks!

Status: Needs review » Needs work

The last submitted patch, menu.inc-_menu_check_access.patch, failed testing.

pillarsdotnet’s picture

Need to read the api docs for watchdog() more carefully...

pillarsdotnet’s picture

Status: Needs work » Needs review

testbot

pillarsdotnet’s picture

Component: field system » menu system

Status: Needs review » Needs work

The last submitted patch, menu.inc-_menu_check_access.patch, failed testing.

pillarsdotnet’s picture

FileSize
655 bytes

Trying again...

pillarsdotnet’s picture

Status: Needs work » Needs review
pillarsdotnet’s picture

Title: Fix _field_ui_view_mode_menu_access() and _menu_translate() functions » _menu_check_access() in includes/menu.inc should check the return value of callback functions.

better title

Status: Needs review » Needs work

The last submitted patch, menu_check_access.patch, failed testing.

Damien Tournoud’s picture

That makes no sense. A NULL value is a set value anyway. If the menu router passed through _menu_translate(), there can be no notice.

pillarsdotnet’s picture

Status: Needs work » Needs review
FileSize
656 bytes

@Damien -- Try running the following code:


function no_value_returned()
{ /* Do nothing and return no value */
}

$foo['bar'] = no_value_returned();

if (isset($foo['bar']))
  { print "The function returned a value.\n";
  }
else
  { print "The function did not return a value.\n";
  }

PHP 5.3 prints "The function did not return a value." Perhaps your PHP behaves differently.

Damien Tournoud’s picture

Status: Needs review » Postponed (maintainer needs more info)

Please re-read #23. What I said is that:

$foo['bar'] = no_value_returned();

if ($foo['bar']) {

}

... doesn't trigger any notice.

Yet it happens that you are trying to fix a Notice: Undefined index, right?

You still haven't described how to reliably reproduce the problem, as a consequence I'm confused about what problem you are actually trying to fix. What I do know is that what you describe in #7 (and the subsequent patches you posted) cannot possibly explain the notice.

pillarsdotnet’s picture

The router item with its NULL value gets serialized and stored in the database, then unserialized and used later.

Try this:

ini_set('display_errors',1);
ini_set('error_reporting',E_ALL);

function no_value_returned()
{ /* Do nothing and return no value */
}

$foo['bar'] = no_value_returned();

$serialized = serialize($foo);

$foo = unserialize($serialized);

$bar = $foo['access'];

Damien Tournoud’s picture

Please re-read your code. You are setting 'bar' and reading 'access'.

pillarsdotnet’s picture

Bah. You're right. But doggone it, SOMEWHERE it was producing errors the other day. Will keep trying. Meanwhile I still assert that the function fails to fulfill its contract, because it promises either TRUE or FALSE and delivers NULL, which is neither.

Damien Tournoud’s picture

I still don't understand what you are up to.

pillarsdotnet’s picture

I'm trying to avoid another six hours sifting through megabytes of debug output trying to nail down why it is that when $item['access'] is set to NULL rather than TRUE or FALSE, a notice later occurs.

Damien Tournoud’s picture

Well, we can definitely fix this menu access callback, but I really doubt it will fix the problem you have been having, which I feel is unrelated.

Damien Tournoud’s picture

Status: Postponed (maintainer needs more info) » Active

Actually, this can only happen in one case: when the 'access callback' function does not exist.

I suggest we just add a error trigger in _menu_check_access() in that case.

pillarsdotnet’s picture

Status: Active » Needs review
FileSize
1005 bytes

Good catch.

bohatt’s picture

pillarsdotnet’s picture

FileSize
1005 bytes

Re-rolled for fuzz.

pillarsdotnet’s picture

Removed what I now understand to be a superfluous watchdog call and re-rolled.

pillarsdotnet’s picture

FileSize
575 bytes

Patch.

pillarsdotnet’s picture

Title: _menu_check_access() in includes/menu.inc should check the return value of callback functions. » _menu_check_access() in includes/menu.inc should warn when the specified callback function does not exist.

Better title.

pillarsdotnet’s picture

Status: Needs review » Closed (duplicate)

Browsing the issue queue, I like the approach in #1059884: Drop function_exists for 'callback' functions better. Marking this issue as duplicate.

andreak’s picture

Many many thanks to whomever posted the following code! You saved me hours of frustration. I had just installed a brand new drupal 7 with the newest ubercart and got the dreaded "undefined index: _menu_check_access() in includes/menu.inc line 776" issue every time I clicked on one of my products grrr. but This fixed it!!!

Now I can sleep....zzz...zzz
-Andrea

diff --git includes/menu.inc includes/menu.inc
index a3121c7..246d076 100644
--- includes/menu.inc
+++ includes/menu.inc
@@ -618,6 +618,13 @@ function _menu_check_access(&$item, $map) {
elseif (function_exists($callback)) {
$item['access'] = call_user_func_array($callback, $arguments);
}
+ else {
+ watchdog('menu.inc',
+ 'Undefined access callback function :callback in router_item :item',
+ array(':callback' => $callback, ':item' => var_export($item,1)),
+ WATCHDOG_WARNING);
+ $item['access'] = FALSE;
+ }
}
}

shanarigelhaupt’s picture

Hello,

I received these errors :

Notice: Undefined index: access in _menu_link_translate() (line 913 of /home/content/22/5654522/html/includes/menu.inc).
Notice: Undefined index: access in _menu_tree_check_access() (line 1488 of /home/content/22/5654522/html/includes/menu.inc).

I see from this thread that there is a patch? How do I install the patch?

Any help would be appreciated.

Thank you!

pillarsdotnet’s picture

Status: Closed (duplicate) » Needs review

Re-opening for 7.x since the 8.x fix will not be backported.

pillarsdotnet’s picture

#37: includes_menu.inc_.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, includes_menu.inc_.patch, failed testing.

pillarsdotnet’s picture

Status: Needs work » Needs review
FileSize
1.03 KB

Re-rolled.

Summit’s picture

Hi, This patch #45 is working for me. Will this be committed soon?
EDIT: sorry, still

Notice: Undefined index: path in domain_nav_menu() (line 34 van sites/all/modules/domain/domain_nav/domain_nav.module

With this patch..

Greetings, Martijn

pillarsdotnet’s picture

@#46 I don't think your error is related to this issue. If you disagree, please walk the rest of us through the code path you think is occurring.

rashvin’s picture

i installed admin_menu again
and deactivated. also cleared all caches

its gone. . .

thank you all

pillarsdotnet’s picture

#45: menu_check_access-994992-45.patch queued for re-testing.

pillarsdotnet’s picture

Updated issue summary. Either this patch or a backport of #1059884: Drop function_exists for 'callback' functions should be reviewed and committed.

itz_andr3’s picture

I got this issue on localhost, while on live hosting not.

Step:
Install clean drupal 7.12
Restore database from live hosting.
Error happened.

Maybe can give an insight, of what might be the problem

Ignore this, i finds out, my module not completely copied

sun’s picture

Title: _menu_check_access() in includes/menu.inc should warn when the specified callback function does not exist. » _menu_check_access() does not warn when the access callback does not exist
Status: Needs review » Needs work
+++ b/includes/menu.inc
@@ -635,6 +635,13 @@ function _menu_check_access(&$item, $map) {
+      watchdog('menu.inc',
+        'Undefined access callback function :callback in router_item :item',
+        array(':callback' => $callback, ':item' => var_export($item,1)),
+        WATCHDOG_WARNING);

1) All on one line, please.

2) Must use t() placeholders instead of db_query() placeholders.

3) Let's use this text: "Undefined access callback %function() for router path @path"

4) WATCHDOG_ERROR is more appropriate.

5) Type/category should be just "menu".

pillarsdotnet’s picture

Re-rolled as requested.

sun’s picture

Status: Needs work » Needs review
FileSize
566 bytes

The message needs to be within the watchdog() call to be compatible with the potx extractor.

Fixed patch attached.

jpstrikesback’s picture

After Applying #54 and upon clearing caches I get:

Notice: Undefined index: _number_parts in _menu_router_build() (line 3624 of /includes/menu.inc) on the Performance page and:

Notice: Undefined index: path in _menu_check_access() (line 641 of /includes/menu.inc)

on a node page

jpstrikesback’s picture

Putting a little isset in there fixes it...does this need tests? (unfortunately I wouldn't know what to test for)

jpstrikesback’s picture

pillarsdotnet’s picture

Putting a little isset in there fixes it...

Patch?

jpstrikesback’s picture

for sure

barraponto’s picture

Status: Needs review » Reviewed & tested by the community

Seems to have fixed the issue.

sun’s picture

Status: Reviewed & tested by the community » Needs work

A router $item without 'path'? Eh?

No, either there's more debugging info for #55, or that case is bogus in itself. In turn, the added isset() hides a deeper error, which is not what we want.

It's late here, so it's possible that I overlook something obvious.

fietserwin’s picture

I got this message on an item in the default navigation menu after the search module was disabled. Clearing the cache solved the problem. But if the menu cache is not cleared automatically, this is a situation that may arise. Thus I guess the patch is correct in solving the problem "by hiding it"., but the addition in #59 (w,r,t, #54) seems to be adding a hiding of a case that should not be hidden. So IMO #54 is correct, while #59 is too forgiving.

a.ross’s picture

Status: Needs work » Reviewed & tested by the community

Agreed. The case in #55 seems to have more issues unrelated to this. Patch in #54 looks good to me. Tentatively marking this RTBC...

webchick’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs tests

I can replicate the issue in #55 with the following:

  $items['a/path'] = array(
    'title' => 'a page',
    'page callback' => 'node_page_default',
    'access callback' => 'blarg',
  );

"Notice: Undefined index: path in _menu_check_access() (line 641 of /Users/abyron/Sites/7.x/includes/menu.inc)."

Needs work, and also needs tests, because that should've been caught.

I wonder if while we're at it we want to include a check for no access callback. This will give you a 403 but it won't tell you why.

webchick’s picture

Category: bug » feature

And also, I would consider this a new feature, not a bug. The bug is in your code. You're asking for additional babysitting, which is something new core wasn't doing for you before.

jpstrikesback’s picture

I haven't been able to replicate the notice anymore, it must have been in a module I updated or stopped using. (also using 7.15 now and iirc it wasn't out at time of posting 55)

pillarsdotnet’s picture

Status: Needs work » Closed (won't fix)

If this is a feature request, then it should be marked "wontfix" because the "feature" of checking for function existence was removed in 8.x.

pillarsdotnet’s picture

Issue summary: View changes

Better issue summary.