Right now if a module wants to add it's own access callback it should menu_alter() the existing one, and usually implement it's own and the original access check.

The patch adds the ability for other modules to add their own access callbacks via drupal_alter() without the need to 'know' which other callbacks are already there.

Also attached a test module that implement's the patch:
1. Install module.
2. Give a user administer block permissions.
3. Enter admin/build/test - access denied.
4. Give same user also administer nodes and go back to the URL - access granted.

The idea came from working on #304196: Using access permissions to define the admin privileges.

Comments

amitaibu’s picture

Assigned: Unassigned » amitaibu
Status: Needs review » Needs work

From IRC:

chx: Amitaibu: you should not call drupal_alter for every access call
chx: Amitaibu: most of the work should be done at build time. maybe use an "access callbacks" key with a value of array('function1' => array('arg1', 'arg2')) and serialize it into the access arguments section and sore something special into the access callback like a - or -1
chx: Amitaibu: so menu rebuild could detect that 'access callback' is an array and act accordingl.y

amitaibu’s picture

Status: Needs work » Needs review
StatusFileSize
new637 bytes
new3.49 KB

Reworked code. Modules can hook_menu_alter() to add more access callbacks.

Follow the steps given in the issue description for the attached test module.

amitaibu’s picture

Status: Needs review » Needs work

from IRC:

chx: Amitaibu: fairly good but i still have some gripes over it
chx: if (is_numeric($callback['callback'])) {
chx: + if (!$return = (bool)$callback['callback']) {
chx: + break;
chx: + }
chx: this part hits me a overtly complex and hard to understand
chx: Amitaibu: also, I think we would better off replacing foreach (unserialize($item['access_arguments']) as $callback) with foreach (unserialize($item['access_arguments']) as $callback => $arguments) , what do you think?

amitaibu’s picture

Status: Needs work » Needs review
StatusFileSize
new6.75 KB

Reworked code to be less ugly...
Also added a simpleTest.

chx’s picture

Status: Needs review » Needs work

We discussed this at length last night. The primary problem here is the two different syntaxes for the callbacks in the singular and the multiple case. I now believe that the only way to achieve a consistent, not-too-ugly syntax is to step on the long path that who knows where ends and change menu items to:


$items['foo/%bar'] = array(
  'callbacks' => array(
    array(
      'type' => 'load',
      // We skip 'callback' here, it's bar_load.
      'arguments' => array(),
    ),
    array(
      'type' => 'access',
      'callback' => 'some_access_callback',
      'arguments' => array(),
    ),
    array(
      'type' => 'access',
      // We can skip 'callback' as it defaults to user_access.
      'arguments' => array(),
    ),
    array(
      'type' => 'page'
      // A followup patch could introduce a path-based default for this callback.
    ),
 ),
);

and then we can have nice followup patches doing wonderful tricks like different title or page callbacks depending on this or that... anyways, the first step to have this callback array.

amitaibu’s picture

StatusFileSize
new11.58 KB

Code still needs work, I have got something wrong with the page callback/arguments.

1. With this patch maybe we can remove from {menu_router} 'access callback' and later 'page callback'?
2. Wouldn't it look nicer if $item will be loaded like this (which might make the scope of this patch larger, so maybe this can be in a follow up patch).


$item['foo/%bar] = array(
  'callbacks' => array(
    'access' => array(
      array(
        'callback' => 'some_foo_callback',
        'arguments' => array(),
      ),
      array(
        'callback' => 'some_bar_callback',
        'arguments' => array(),
      ),
    ),
    'page' => array(
      array(
        'callback' => 'some_baz_callback',
        'arguments' => array(),
      ),
    ),
);
amitaibu’s picture

StatusFileSize
new12.92 KB

This patch is already working.

amitaibu’s picture

StatusFileSize
new13.15 KB

After discussion with chx cleaned the code and placed also block and title callbacks in the new structure.
Code already seems to be working, but some optimization might be needed(?)

Patch still doesn't deal with removing the access_argument from the {menu_router}

amitaibu’s picture

StatusFileSize
new12.56 KB

Following chx remarks from IRC:
* Fixed ['0'] to be [0].
* Fixed indention.
* Fixed arguments being checked for parent without callback missing (access and page).
* $group_callbacks is now $item['group_callbacks'].
* page, title and block are now under $item['group_callbacks'][TYPE].

chx’s picture

the point in storing group_callback in item was to skip foreach ($parent['callbacks'] as $parent_callback) -- you can peruse the parent's group_callback['page'] instead and so on.

amitaibu’s picture

StatusFileSize
new12.42 KB

Indeed, this allowed reworking the code and removing a lot of redundant work.

pwolanin’s picture

Need to read in detail - but I'm not sure how the same code can work for titles and access. With access, all we care is whether the next result of AND-ing all the results is TRUE or FALSE. For titles, etc the order matter - i.e. the last (or first?) callback wins.

amitaibu’s picture

@pwolanin,
Patch is trying to accomplish two things:
1. Accept a change in the hook_menu() structure which will be done once this patch is ready - as in comment #5.
2. Allow only the access callback to have multiple callbacks.
So to your question, the title still can have only a single callback.

chx’s picture

Status: Needs work » Needs review

We need opinions from the community, which structure would you like, the one in #5 which is a consistent list of callback arrays or a less consistent theme_table like arrangement where the type is the key and each value either can be a callback array or a list of callback arrays:

'callback' => array(
  'page' => array(
    'callback' => ...
  ),
  'access' => array(
    array(
      'callback' => ...,
    ),
  ),
),

It still would be just an option to use either one or many access callbacks. @pwolanin, this is mostly just a notation change for everything but the access callbacks (for now).

Crell’s picture

Let's simplify even further:

$items['my/path'] = array(
  'page' => array(
    'callback' => 'myfunc',
    'arguments' => array(),
  ),
  'access' => array(
    array('callback' => 'user_access', 'arguments' => array('do stuff')),
    array('arguments' => array('do more'),  // callback still defaults to 'user_access'
  ),
);

Of course, that then begs the question of whether we should move title into its own arrays or as a sub-item of page...

chx’s picture

Well, yeah the callback wrapper was only necessary for #5... any more feedback?

Status: Needs review » Needs work

The last submitted patch failed testing.

amitaibu’s picture

1. Since we have $title => foo lets keep callbacks.
2. Use Crell's idea about having title under page.

How about this:

$items['my/path'] = array(
  'title' => 'foo',
  'callbacks' => array(
    'page' => array(
      'callback' => 'myfunc',
      'arguments' => array(),
      'title' => array('callback' => 'bar', 'arguments' => array('baz')),
    ),
    'access' => array(
      array('callback' => 'user_access', 'arguments' => array('do stuff')),
      array('arguments' => array('do more'),  // callback still defaults to 'user_access'
    ),
  ),
);
Crell’s picture

Why add yet another layer of array in there? It doesn't buy us anything.

amitaibu’s picture

StatusFileSize
new11.56 KB

Patch according to this structure:

$items['my/path'] = array(
  'title' => 'foo',
  'page' => array(
    'callback' => 'myfunc',
    'arguments' => array(),
    'title' => array('callback' => 'bar', 'arguments' => array('baz')),
  ),
  'access' => array(
    array('callback' => 'user_access', 'arguments' => array('do stuff')),
    array('arguments' => array('do more'),  // callback still defaults to 'user_access'
  ),
);
Crell’s picture

Why the double "title" entry?

amitaibu’s picture

Discussed with Crell on IRC. He has some more plans about this patch, that he will explain in a follow up issue. Until then, here's a re-roll of the patch according to this structure:

$items['my/path'] = array(
  'page' => array(
    'callback' => 'myfunc',
    'arguments' => array(),
    'title' => array(  'title' => 'foo', 'callback' => 'bar', 'arguments' => array('baz')),
  ),
...

We aren't dealing with multiple pages and titles, but we lay down the ground for it.

chx’s picture

  1. title => 'foo should still work
  2. title => array('callback' => , 'arguments' => '') should also work.

This simplifies the API (which currently supports like four different title combinations and is confusing thusly) and still the common case is simple.

Edit: defaulting the callback to t is fine.

Question, if a callback array lacks the 'callback' callkey, so it's only array('arguments' => array()) wouldn't it make sense to simply drop the wrapper and make the callback array just the arguments? this allows 'title' => array('some @placeholder', array('@placeholder' => #foo)) just work. Too much magic?

Crell’s picture

Too much magic. I wouldn't want to have to write the parser for that, much less have to read it. :-)

To clarify something I said to Amitaibu in IRC, if title is within each page entry then it allows us to define a different title for each of multiple page callbacks on a given page (which is a long-term goal of mine, although not in this patch) but we have to define it each time. If title is defined directly on the path, then we only have to define it once but can't make it differ depending on the page callback that is used. If we allow both, so that the inner one overrides the outer one if specified, then we get the best of both worlds but I don't want to have to write the parsing logic for that.

Also, chx had mentioned in Szeged renaming hook_menu to something with "router" in it to make it less confusing, since it's distinct from menu link behavior. If we're going to be breaking hook_menu syntax in this patch anyway, I recommend we go ahead and rename the hook to hook_router_info() at the same time. That way we only have to break every file in Drupal once, not twice.

amitaibu’s picture

StatusFileSize
new12.06 KB

In this patch all title keys are under page, however:
+ // Page title can be in $item['title'] or in $item['page']['title']['title']
+ // We are giving the later a priority in case we have duplicate entries.

chx, are you ok with it, or do you think that title + title argument + title arguments should be able to exist also outside of page?

amitaibu’s picture

Discussed with Crell and chx in IRC and determined to remove title from page and follow #23.

chx: Amitaibu: I think this patch should be about "unified/simplified menu API syntax" which happens to include a) mutiply access callback handling b) simplified title callback handling.
...
chx: Crell: this patch is more about a unified callback array. array('acccess' => [callbackarray], 'title' => [callbackarray], 'page' => [callbackarray]) and so on.
...
Crell: chx: I originally asked it, not dictated it. :-) Once we support multiple page callbacks we really should move it down, but for now I'm OK with saving kittens.

will re-roll soon.

amitaibu’s picture

StatusFileSize
new12.34 KB

With this patch we can also get rid of the 'title' column in the {menu_router} as it's now stored in 'table arguments'.

I have noticed that in _menu_item_localize() we have this line
$item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map));

which is never run and doesn't make sense (i.e. define title and title arguments), thus removed.

amitaibu’s picture

StatusFileSize
new12.24 KB

Re-rolled patch after DBTNG.

amitaibu’s picture

Since this issue will require a big find & replace in all module core's hook_menu(), maybe we can continue with separate kitten-friendly patches:

1. The current patch which lays the ground of the API change + multiple access callbacks.
2. find & replace hook_menu().
3. Add simpleTest for multiple access callback.
4. Remove from {menu_router} 'access callback' and 'title'.

amitaibu’s picture

StatusFileSize
new12.23 KB

Ok, changing status to CNR. re-rolled on a minor issue where == FALSE was used instead of !

amitaibu’s picture

Status: Needs work » Needs review
StatusFileSize
new12.23 KB

hmm, now changing the status for real. Re-uploaded patch so it is tested.

Status: Needs review » Needs work

The last submitted patch failed testing.

amitaibu’s picture

Status: Needs work » Needs review
StatusFileSize
new13.6 KB

Thank you test bot - indeed _menu_item_localize() wasn't handled properly.

pwolanin’s picture

Status: Needs review » Needs work

I really don't see the benefit of changing 'page callback' and 'page arguments' to 'page' => array(...) if they are being stored the same in the DB. This patch has serious scope creep - if the goal is to allow multiple access callbacks - let's have a patch that does just that.

Is the patch missing a needed schema change?

+      'access_callback' => '',
+      'access_arguments' => serialize($item['access']),

also, ugh:

+        if (isset($callback['callback'])) {
+          if (is_bool($callback['callback'])) {
+            $callback['callback'] = intval($callback['callback']);
           }

this would be way cleaner if we used constants as chx proposed like MENU_ACCESS_ALWAYS

pwolanin’s picture

this comment doesn't really make sense:

+      // Pass by reference as we might change the callback itself.
+      foreach ($item['access'] as $key => &$callback) {

since we are not actually passing it.

Also,should there be a ksort() for the access array?

+    // If menu has multiple access and one access is FALSE then deny access
+    // and stop checking other access callbacks.
+    if ($item['access'] == FALSE) {
+      break;

or something since some modules may know that there callback should tend to go first (likely to return FALSE) or last (unlikely).

I guess they could use array_unshift vs. pushing

Crell’s picture

Re #34, I think the idea is that if we're going to switch to a deeper array, we should do so consistently. We should also do so once, so that we don't have to break the API twice as doing so makes a bigger patch. :-)

pwolanin’s picture

@Crell- well each of these sets of callbacks is handled separately, so I don't think makeing a change incrementally would be much more work.

In any case, if we want to switch to this nested system for just 'access' or for all, then the patch needs to change the schema, all hook_menu implementations in core, etc, to reflect it.

amitaibu’s picture

Title: Allow multiple access callbacks per menu item. » Allow multiple access callbacks per menu item by changing hook_menu() API
StatusFileSize
new20.25 KB

pwolanin said:

In any case, if we want to switch to this nested system for just 'access' or for all, then the patch needs to change the schema, all hook_menu implementations in core, etc, to reflect it.

ok, so let's try it this way:
* Patch includes the API change and multiple access callback
* Changed title to reflect that.
* Patch changes schema by removing 'title', 'access callback'.

Two questions:
1. I've removed from menu_local_tasks() ->orderBy('title'). If we do want to sort by title we'll have to do it in several steps as the title is seralized in title_arguments.
2. I wanted to change the access_arguments in {menu_router} to access, but then it collides with $item['access'] in _menu_check_access(), So I kept the name as is, just altered the column description.

amitaibu’s picture

Status: Needs work » Needs review
StatusFileSize
new20.17 KB

Last patch had some simpletest exceptions, which seem to be fixed in this one, setting to CNR, so the testbot will pick it up.

@chx, Maybe it's a good time to create a patch for core's hook_menu(), can you help me with that?

amitaibu’s picture

StatusFileSize
new23.2 KB

Now with a simpletest to test multiple access callbacks.

pwolanin’s picture

there seems to be some confusion/inconsistency in the patch around the title stuff. e.g. you build $item['title'] but are then pulling out the components to save separately.

amitaibu’s picture

Status: Needs review » Needs work

Indeed, I'll re-work it, so we'll end up eliminating 'title_callback' from {menu_router} as well.

chx’s picture

Are you sure that's the best way to go? As long as you do not have multiple titles , having a title callback and a title arguments is just fine.

amitaibu’s picture

We can come to using only the X_arguments, i.e.

  'access_arguments' => serialize($item['access']),
  'page_arguments' => serialize($item['page']),
  'title_arguments' => serialize($item['title']),
  ...

which will remove title, title_callback and page_callback. The reason would be consistency in the code, but if it's not needed i'll be happy to let it go and go back to the patch in #40 ;)

amitaibu’s picture

StatusFileSize
new23.2 KB

re-roll to keep in synch with HEAD

catch’s picture

Version: 7.x-dev » 8.x-dev
Category: feature » task
sun’s picture

Issue tags: +MenuSystemRevamp

.

Crell’s picture

Status: Needs work » Closed (duplicate)

This is now possible thanks to the new Access Checker system.