right now, this is the essence of the page manager menu delegation logic:

  $tasks = page_manager_get_tasks();

  // Provide menu items for each task.
  foreach ($tasks as $task_id => $task) {
    $handlers = page_manager_get_task_handler_plugins($task);
    // Allow the task to add its own menu items.
    if ($function = ctools_plugin_get_function($task, 'hook menu')) {
      $function($items, $task);
    }

    // And for those that provide subtasks, provide menu items for them, as well.
    foreach (page_manager_get_task_subtasks($task) as $subtask_id => $subtask) {
      // Allow the task to add its own menu items.
      if ($function = ctools_plugin_get_function($task, 'hook menu')) {
        $function($items, $subtask);
      }
    }
  }

first, a simple problem - why are we running page_manager_get_task_handler_plugins($task); at all? we don't pass those $handlers anywhere, and that function doesn't do anything by reference. seems like cruft, unless the point is loading all the handlers in order to reveal their own hook_menu implementations for delegated inclusion. i don't see where that would happen, though.

bigger problem is this, though:

    // And for those that provide subtasks, provide menu items for them, as well.
    foreach (page_manager_get_task_subtasks($task) as $subtask_id => $subtask) {
      // Allow the task to add its own menu items.
      if ($function = ctools_plugin_get_function($task, 'hook menu')) {

we're iterating over the subtasks, but still searching for the plugin function on the parent task. which means the parent's hook menu gets called once for each of the subtasks it has. yikes. my initial thought was to just change this, but it seems like the page plugin is indeed adding more on each passthrough.

i could relatively easily work around this for my purposes simply by using the hook menu alter callback instead, as it has neither of the problems that hook menu suffers from:

  $tasks = page_manager_get_tasks();

  foreach ($tasks as $task) {
    if ($function = ctools_plugin_get_function($task, 'hook menu alter')) {
      $function($items, $task);
    }
    // let the subtasks alter the menu items too.
    foreach (page_manager_get_task_subtasks($task) as $subtask_id => $subtask) {
      if ($function = ctools_plugin_get_function($subtask, 'hook menu alter')) {
        $function($items, $subtask);
      }
    }
  }

however, this seems like an api consistency/sanity issue, so i thought i'd raise it in a permanent forum so we can more easily figure out the course of action we want to take.

Comments

sdboyer’s picture

actually, i was wrong - the page hook_menu implementation doesn't seem to add anything beyond the first one. so it seems like we're just reprocessing everything on menu rebuild for no particular reason :)

so barring any concerns, i'm gonna fix those two lines to be the exact same for hook menu as for hook menu alter.

merlinofchaos’s picture

Well, switching to alter changes the signature for any existing subtasks outside of CTools. I realize there is a low probability of them existing. But that's one thing to consider.

Changing to an alter changes the order the items appear. That could potentially be an issue for other things also doing an alter for page manager items.

I do agree that fetching the handlers seems to be cruft.

You mention 'workaround' which means you have an actual problem? Because the only problem you've identified is inefficient code and maybe poor DX, but these arent things you need to work around.

sdboyer’s picture

Status: Active » Postponed

OK, i've pushed a commit removing the crufty call, for starters. as for the other bit, transcript of our irc conversation:

<sdboyer> merlinofchaos: yeah the issue is actually that different data comes into the hook
<sdboyer> the hook itself would need to internally detect whether it's being called with a subtask PDA or the main task PDA
<merlinofchaos> Well, that's doable just by adding data to the subtask so you can tell the difference.
<sdboyer> which is at least counter to developer expectations, those being that a given hook_menu implementation should probably only be fired once per hook_menu call
<sdboyer> sure
<sdboyer> it's not at all that it can't be worked around
<sdboyer> it's that the same callback is fired for both task and subtask that runs counter to expectations
-*- sdboyer goes to find the code he was working with
<sdboyer> it works pretty well for page.inc b/c fairly little is drawn from the task array...or because the subtask PDA contains most of what's in the parent
<sdboyer> but this: http://pastebin.com/3ZwFeBrS
<sdboyer> fails if both path and file aren't set in the subtask
<merlinofchaos> Hm.
<sdboyer> the way i've set it up setting path isn't necessarily needed (though it could be added)
<sdboyer> but file doesn't even really make sense
<sdboyer> what ends up happening is that gets called the first time for the task, and it works
<sdboyer> then it gets re-called for each subtask and fills in null values, and those override the original task's items
<sdboyer> i haven't dug all the way down in the page.admin.inc implementation, but i did look to verify that it seems to fill in exactly the same set of info each time through, regardless of whether it's being called with task or subtask info
<merlinofchaos> sdboyer: My concern is breaking API for existing people.
<merlinofchaos> That's always ass.
<sdboyer> merlinofchaos: yeah mine too
<sdboyer> yup
<sdboyer> i mean we could increment the api version on tasks
<merlinofchaos> Yeah but that still means people will have to update code. I'd really like to avoid that.
<merlinofchaos> It has to be something really severe to force that.
<sdboyer> merlinofchaos: yeah, fair

so, gonna mark this as postponed until we decide how we want to handle this mini-breakage for api consumers.

  • sdboyer committed e92c772 on 8.x-2.x
    Issue #1611252: Remove a crufty call from page_manager_menu().
    
    

  • sdboyer committed e92c772 on 8.x-3.x
    Issue #1611252: Remove a crufty call from page_manager_menu().