This code was built for a project which included:

  • the ability to create menus
  • the ability to have duplicated menus
  • having a sidebar that contains the current selected menu

By adding node/1?b=1 at the end of the menus that were duplicated, a simple operation could verify if they menu was selected or not.

Fixing duplication requires that everytime a menu is saved, we go through the menu structure again to fix the problematic menus appending ?b=X at the end. You can either push a button in the admin interface or just modify them, as it hook on menu_link_alter.

<?php

function duplicated_menus_menu() {
  $items['admin/settings/duplicated_menus'] = array(
    'title' => 'Duplicated menus admin page', 
    'page callback' => 'drupal_get_form', 
    'page arguments' => array('duplicated_menus_admin_form'),
    'access arguments' => array('access content'), 
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}
function duplicated_menus_admin_form() {
  $form = array();
  $form['description'] = array(
    '#type' => 'markup',
    '#value' => t('This button force reloading the primary menus to add proper menu numbering allowing duplicates to have their own predefinite paths'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Reload primary links'),
  );
  return $form;
}

function duplicated_menus_admin_form_submit($form, &$form_state) {
  _duplicated_menus_rebuild_primary();
}

function _duplicated_menus_get_dupes($level, $current, $list, $known_paths) {
  $ids = 0;
#  drupal_set_message('recursion_called, level = ' . count($current));
  if(!is_array($level)) {
    // drupal_set_message(print_r($level,1));
  } else {

    foreach($level as $weighted_mlid => $item) {
#      drupal_set_message($weighted_mlid .' rec dupes');
      //drupal_set_message(print_r($current,1) . ', list is ' . print_r($list, 1));
      $subcurrent = $current;
      // This gets the dupes, regardless of their previous treatment as dupe
      $the_path = preg_replace('/\?b=(\d)+/', '', $item['link']['link_path']);
      array_push($subcurrent, $ids);
      if (!in_array($the_path, array_keys($known_paths))) {
        $known_paths[$the_path] = array($subcurrent, $item['link']['mlid']);
      } else {
  #      drupal_set_message(print_r($list,1));
         
        if (in_array($the_path, array_keys($list))) {
 #         drupal_set_message('exists');
          
          $list[$the_path][] = array($subcurrent, $item['link']['mlid']);
#          drupal_set_message('after:' . print_r($list[$the_path], 1));

        } else {
          $werd = menu_link_load($known_paths[$the_path][1]);
 #         drupal_set_message('weights: ' . $werd['weight'] .'and'. $item['link']['weight'] . ' and '. $item['link']['plid'] .' plids '.$werd['plid']);
#          drupal_set_message(print_r($item['link'],1));
          if($werd['plid'] == $item['link']['plid']) {
            if ($werd['weight'] > $item['link']['weight']) {
              $list[$the_path] = array(1 => $known_paths[$the_path], 0 => array($subcurrent, $item['link']['mlid']));
            } else {
              $list[$the_path] = array(0 => $known_paths[$the_path], 1 => array($subcurrent, $item['link']['mlid']));
            }
          } else {
            $list[$the_path] = array(0 => $known_paths[$the_path], 1 => array($subcurrent, $item['link']['mlid']));
          }
            
          #drupal_set_message('built the first 2,' . print_r($list[$the_path], 1) . ' known_path being ' . print_r($known_paths[$the_path], 1) . ' and weighted_mlids being' . $weighted_mlid);
        }
      } 
      if (is_array($item['below']) && count($item['below'])) {
        list($l, $kp) = _duplicated_menus_get_dupes($item['below'], $subcurrent, $list, $known_paths);
        $list = array_merge($list, $l);
        $known_paths += $kp;
      }
      $ids++;
    }
  }
#  drupal_set_message('out of rec, going back to = ' . (count($current) - 1));
  return array($list, $known_paths);
}

function _duplicated_menus_rebuild_primary() {
  menu_cache_clear_all();
  $menu = menu_tree_all_data('primary-links');
  $fe = array();
  list($list, $known_paths) = _duplicated_menus_get_dupes($menu, array(), $fe, array());
 # drupal_set_message(print_r($list,1));
  foreach ($list as $path => $item) {
    $items = array();
    foreach($item as $count => $val) {
      list($address, $mlid) = $val; 
      $items[] = $mlid;
      $obj = menu_link_load($mlid);
      $obj['link_path'] = preg_replace('/\?b=(\d)+/', '', $obj['link_path']);
      unset($obj['options']['attributes']['query']);
      if ($obj['options']['query'] != "b=$count" || $obj['localized_options']['query'] != "b=$count") {
        $obj['options']['query'] = "b=$count";
        $obj['localized_options']['query'] = "b=$count";
        $obj['do_not_rebuild'] = 1;
  #      drupal_set_message('menu: ' . $obj['mlid'] . ' got altered, with count='. $count . ' opt ='. $obj['options']['query']);
#      drupal_set_message('<pre>' . print_r($obj,1));
        if (!menu_link_save($obj)) {
          drupal_set_message(t('There was an error saving the menu link.'), 'error');
        }
      }
     #   $obj2 = menu_link_load($obj['mlid']);
     #   while ($obj2['options']['query'] != $obj['options']['query']) {
       #   menu_link_save($obj);
      #    $obj2 = menu_link_load($obj['mlid']);
       # }
    }
    #drupal_set_message('path: ' . $path . ' contained ' . print_r($items,1));
    
  }
#  drupal_set_message('done rebuilding...');
}

function duplicated_menus_menu_link_alter(&$item, $menu) {
  if (!$item['do_not_rebuild']) {
  #  drupal_set_message("menu_link_alter got called");
    $item['do_not_rebuild'] = 1;
    // this sounds like: it will duplicate and fuck everything up
    // but because it is saved an mlid will be set, its totally fine
    // we need to save to alter the weight
#    drupal_set_message('in MLA' . $item['weight'] . $item['link_title']);
    if (!menu_link_save($item)) {
      drupal_set_message(t('There was an error saving the menu link.'), 'error');
    }
    _duplicated_menus_rebuild_primary();
#    drupal_set_message('saved now rebuilding');
    $item2 = menu_link_load($item['mlid']);
    $item['options'] = $item2['options'];
  }
}

function _duplicated_menus_recurse_menu_setintrail($menu, $deselectors, $val) {
  $key = array_keys($menu);
  if (is_array($deselectors) && !empty($deselectors)) {
    $num = array_shift($deselectors);
  }
  $menu[$key[$num]]['link']['in_active_trail'] = $val;
  if (is_array($menu[$key[$num]]['below']) && count($menu[$key[$num]]['below']) > 0 && count($deselectors)) {
    $menu[$key[$num]]['below'] = _duplicated_menus_recurse_menu_setintrail($menu[$key[$num]]['below'], $deselectors, $val);
  } 
  return $menu;
}

function duplicated_menus_block($op = 'list', $delta = 0) {
  if ($op == 'list') {
    $blocks[0] = array(
      'info' => t('Current menu'), 
      'weight' => 0, 
      'status' => 1, 
      'region' => 'left',
    );
    return $blocks;
  } else if ($op == 'view') {
    switch ($delta) {
      case 0:
        // Your module will need to define this function to render the block.
        $block = array(
          'subject' => t(''), 
          'content' => duplicated_menus_current_menus(),
        );
        break;
    }
  }
  return $block;
}

function duplicated_menus_current_menus() {
  $menus = menu_tree_page_data('primary-links');
  if (arg(0) != 'admin') {
    $fe = array();
    list($redirects, $known_paths) = _duplicated_menus_get_dupes($menu, array(), $fe, array());
    foreach(array_keys($redirects) as $item) {
      $parts = explode('/', $item);
        $i = 0;
        $is_it_it = 1;
        foreach($parts as $part) {
          if ($part != arg($i) && $part != '*') {
            $is_it_it = 0;
          }
          $i++;
        }
        if ($is_it_it && isset($_GET['b'])) {  // if its a redirection (b != 0) we just set in trail ,0 to unset, then same thing, 1 to set the new value
          $menus = _duplicated_menus_recurse_menu_setintrail($menus, $redirects[$item][0][0], 0);
          $menus = _duplicated_menus_recurse_menu_setintrail($menus, $redirects[$item][$_GET['b']][0], 1);
        } elseif($is_it_it && (count($redirects[$item][0]) == 0)) { // otherwise, we just set
          $menus = _duplicated_menus_recurse_menu_setintrail($menus, $redirects[$item][0][0], 1);
      }
    }
    foreach($menus as $systitle => $data) {
      if ($data['link']['in_active_trail']) {
        $menu = $data;
        // FALSE && FALSE == TRUE
        if (isset($data['below']) && is_array($data['below'])) {
          unset($menu['below']) ; 
          $output = '<div id="primary-links-sub">' . menu_tree_output(array("blah"=>$menu) + $data['below']) . '</div>';
        } else {
          $output = '<div id="primary-links-sub">' . menu_tree_output(array("blah"=>$menu)) . '</div>';
        }
        return $output;
      }
    }
  }
}
 
function duplicated_menus_fix_menus($menus) {
  $keys = array_keys($menus);
   $menu = menu_tree_all_data('primary-links');
  $fe = array();
  list($redirects, $known_paths) = _duplicated_menus_get_dupes($menu, array(), $fe, array());
  
  if (isset($_GET['b'])) {
    foreach(array_keys($redirects) as $item) {
      $parts = explode('/', $item);
      $i = 0;
      $is_it_it = 1;
      foreach($parts as $part) {
        if ($part != arg($i) && $part != '*') {
          $is_it_it = 0;
        }
        $i++;
      }
      if ($is_it_it) {  // this is so self explanatory it shouldn't need comment
        $menus = _duplicated_menus_recurse_menu_setintrail($menus, $redirects[$item][0][0], 0);
        $menus = _duplicated_menus_recurse_menu_setintrail($menus, $redirects[$item][$_GET['b']][0], 1);
      }
    }
  }
  return $menus;
}
?>

and duplicated_menu.info :

; $Id: calendar.info,v 1.8 2008/04/23 23:42:53 karens Exp $
name = duplicated_menus
description = Allow duplicated menus
core = 6.x