/**
  * Merge a custom menu with the current default WordPress menu. Adds/replaces defaults,
  * inserts new items and removes missing items.
  *
  * @uses self::$item_templates
  *
  * @param array $tree A menu in plugin's internal form
  * @return array Updated menu tree
  */
 function menu_merge($tree)
 {
     //Iterate over all menus and submenus and look up default values
     //Also flag used and missing items.
     $orphans = array();
     foreach ($tree as &$topmenu) {
         if (!ameMenuItem::get($topmenu, 'custom')) {
             $template_id = ameMenuItem::template_id($topmenu);
             //Is this menu present in the default WP menu?
             if (isset($this->item_templates[$template_id])) {
                 //Yes, load defaults from that item
                 $topmenu['defaults'] = $this->item_templates[$template_id]['defaults'];
                 //Note that the original item was used
                 $this->item_templates[$template_id]['used'] = true;
             } else {
                 //Record the menu as missing, unless it's a menu separator
                 if (empty($topmenu['separator'])) {
                     $topmenu['missing'] = true;
                     $temp = ameMenuItem::apply_defaults($topmenu);
                     $temp = $this->set_final_menu_capability($temp);
                     $this->add_access_lookup($temp, 'menu', true);
                 }
             }
         }
         if (is_array($topmenu['items'])) {
             //Iterate over submenu items
             foreach ($topmenu['items'] as &$item) {
                 if (!ameMenuItem::get($item, 'custom')) {
                     $template_id = ameMenuItem::template_id($item);
                     //Is this item present in the default WP menu?
                     if (isset($this->item_templates[$template_id])) {
                         //Yes, load defaults from that item
                         $item['defaults'] = $this->item_templates[$template_id]['defaults'];
                         $this->item_templates[$template_id]['used'] = true;
                         //We must move orphaned items elsewhere. Use the default location if possible.
                         if (isset($topmenu['missing']) && $topmenu['missing']) {
                             $orphans[] = $item;
                         }
                     } else {
                         if (empty($item['separator'])) {
                             //Record as missing, unless it's a menu separator
                             $item['missing'] = true;
                             $temp = ameMenuItem::apply_defaults($item);
                             $temp = $this->set_final_menu_capability($temp);
                             $this->add_access_lookup($temp, 'submenu', true);
                         }
                     }
                 } else {
                     //What if the parent of this custom item is missing?
                     //Right now the custom item will just disappear.
                 }
             }
         }
     }
     //If we don't unset these they will f**k up the next two loops where the same names are used.
     unset($topmenu);
     unset($item);
     //Now we have some items marked as missing, and some items in lookup arrays
     //that are not marked as used. Lets remove the missing items from the tree.
     $tree = ameMenu::remove_missing_items($tree);
     //Lets merge in the unused items.
     foreach ($this->item_templates as $template_id => $template) {
         //Skip used menus and separators
         if (!empty($template['used']) || !empty($template['defaults']['separator'])) {
             continue;
         }
         //Found an unused item. Build the tree entry.
         $entry = ameMenuItem::blank_menu();
         $entry['template_id'] = $template_id;
         $entry['defaults'] = $template['defaults'];
         $entry['unused'] = true;
         //Note that this item is unused
         //Add the new entry to the menu tree
         if (!empty($template['defaults']['parent'])) {
             if (isset($tree[$template['defaults']['parent']])) {
                 //Okay, insert the item.
                 $tree[$template['defaults']['parent']]['items'][] = $entry;
             } else {
                 //This can happen if the original parent menu has been moved to a submenu.
                 $tree[$template['defaults']['file']] = $entry;
             }
         } else {
             $tree[$template['defaults']['file']] = $entry;
         }
     }
     //Move orphaned items back to their original parents.
     foreach ($orphans as $item) {
         $defaultParent = !empty($item['defaults']['parent']) ? $item['defaults']['parent'] : null;
         if (isset($defaultParent) && isset($tree[$defaultParent])) {
             $tree[$defaultParent]['items'][] = $item;
         } else {
             //This can happen if the parent has been moved to a submenu.
             //Just put the orphan at the bottom of the menu.
             $tree[$item['defaults']['file']] = $item;
         }
     }
     //Resort the tree to ensure the found items are in the right spots
     $tree = ameMenu::sort_menu_tree($tree);
     return $tree;
 }
 /**
  * Merge a custom menu with the current default WordPress menu. Adds/replaces defaults,
  * inserts new items and removes missing items.
  *
  * @uses self::$item_templates
  *
  * @param array $tree A menu in plugin's internal form
  * @return array Updated menu tree
  */
 function menu_merge($tree)
 {
     //Iterate over all menus and submenus and look up default values
     //Also flag used and missing items.
     $orphans = array();
     //Build an index of menu positions so that we can quickly pick the right position for new/unused items.
     $positions_by_template = array();
     $following_separator_position = array();
     $previous_default_top_menu = null;
     foreach ($tree as &$topmenu) {
         if (!empty($topmenu['separator']) && isset($previous_default_top_menu)) {
             $following_separator_position[$previous_default_top_menu] = ameMenuItem::get($topmenu, 'position', 0);
         }
         $previous_default_top_menu = null;
         if (!ameMenuItem::get($topmenu, 'custom')) {
             $template_id = ameMenuItem::template_id($topmenu);
             //Is this menu present in the default WP menu?
             if (isset($this->item_templates[$template_id])) {
                 //Yes, load defaults from that item
                 $topmenu['defaults'] = $this->item_templates[$template_id]['defaults'];
                 //Note that the original item was used
                 $this->item_templates[$template_id]['used'] = true;
                 //Add valid, non-custom items to the position index.
                 $positions_by_template[$template_id] = ameMenuItem::get($topmenu, 'position', 0);
                 $previous_default_top_menu = $template_id;
             } else {
                 //Record the menu as missing, unless it's a menu separator
                 if (empty($topmenu['separator'])) {
                     $topmenu['missing'] = true;
                     $temp = ameMenuItem::apply_defaults($topmenu);
                     $temp = $this->set_final_menu_capability($temp);
                     $this->add_access_lookup($temp, 'menu', true);
                 }
                 //Don't add missing menus to the index because they won't show up anyway.
             }
         }
         if (is_array($topmenu['items'])) {
             //Iterate over submenu items
             foreach ($topmenu['items'] as &$item) {
                 if (!ameMenuItem::get($item, 'custom')) {
                     $template_id = ameMenuItem::template_id($item);
                     //Is this item present in the default WP menu?
                     if (isset($this->item_templates[$template_id])) {
                         //Yes, load defaults from that item
                         $item['defaults'] = $this->item_templates[$template_id]['defaults'];
                         $this->item_templates[$template_id]['used'] = true;
                         //Add valid, non-custom items to the position index.
                         $positions_by_template[$template_id] = ameMenuItem::get($item, 'position', 0);
                         //We must move orphaned items elsewhere. Use the default location if possible.
                         if (isset($topmenu['missing']) && $topmenu['missing']) {
                             $orphans[] = $item;
                         }
                     } else {
                         if (empty($item['separator'])) {
                             //Record as missing, unless it's a menu separator
                             $item['missing'] = true;
                             $temp = ameMenuItem::apply_defaults($item);
                             $temp = $this->set_final_menu_capability($temp);
                             $this->add_access_lookup($temp, 'submenu', true);
                         }
                     }
                 } else {
                     //What if the parent of this custom item is missing?
                     //Right now the custom item will just disappear.
                 }
             }
         }
     }
     //If we don't unset these they will f**k up the next two loops where the same names are used.
     unset($topmenu);
     unset($item);
     //Now we have some items marked as missing, and some items in lookup arrays
     //that are not marked as used. Lets remove the missing items from the tree.
     $tree = ameMenu::remove_missing_items($tree);
     //Lets merge in the unused items.
     $max_menu_position = !empty($positions_by_template) ? max($positions_by_template) : 100;
     foreach ($this->item_templates as $template_id => $template) {
         //Skip used menus and separators
         if (!empty($template['used']) || !empty($template['defaults']['separator'])) {
             continue;
         }
         //Found an unused item. Build the tree entry.
         $entry = ameMenuItem::blank_menu();
         $entry['template_id'] = $template_id;
         $entry['defaults'] = $template['defaults'];
         $entry['unused'] = true;
         //Note that this item is unused
         if ($this->options['unused_item_position'] === 'relative') {
             //Attempt to maintain relative menu order.
             $previous_item = $was_separated = null;
             if (isset($this->relative_template_order[$template_id])) {
                 $previous_item = $this->relative_template_order[$template_id]['previous_item'];
                 $was_separated = $this->relative_template_order[$template_id]['was_previous_item_separated'];
             }
             if (isset($previous_item, $positions_by_template[$previous_item])) {
                 if ($was_separated && isset($following_separator_position[$previous_item])) {
                     //Desired order: previous item -> separator -> this item.
                     $entry['position'] = $following_separator_position[$previous_item];
                 } else {
                     //Desired order: previous item -> this item.
                     $entry['position'] = $positions_by_template[$previous_item];
                     if (isset($following_separator_position[$previous_item])) {
                         //Now the separator is after this item, not the previous one.
                         $following_separator_position[$template_id] = $following_separator_position[$previous_item];
                         unset($following_separator_position[$previous_item]);
                     }
                 }
                 $entry['position'] = $entry['position'] + 0.01;
             } else {
                 if ($previous_item === '') {
                     //Empty string = this was originally the first item.
                     $entry['position'] = -1;
                 } else {
                     //Previous item is unknown or doesn't exist. Leave this item in its current, incorrect position.
                 }
             }
         } else {
             //Move unused entries to the bottom.
             $max_menu_position = $max_menu_position + 1;
             $entry['position'] = $max_menu_position;
         }
         $positions_by_template[$template_id] = ameMenuItem::get($entry, 'position', 0);
         //Add the new entry to the menu tree
         if (isset($template['defaults']['parent'])) {
             if (isset($tree[$template['defaults']['parent']])) {
                 //Okay, insert the item.
                 $tree[$template['defaults']['parent']]['items'][] = $entry;
             } else {
                 //This can happen if the original parent menu has been moved to a submenu.
                 $tree[$template['defaults']['file']] = $entry;
             }
         } else {
             $tree[$template['defaults']['file']] = $entry;
         }
     }
     //Move orphaned items back to their original parents.
     foreach ($orphans as $item) {
         $defaultParent = $item['defaults']['parent'];
         if (isset($defaultParent) && isset($tree[$defaultParent])) {
             $tree[$defaultParent]['items'][] = $item;
         } else {
             //This can happen if the parent has been moved to a submenu.
             //Just put the orphan at the bottom of the menu.
             $tree[$item['defaults']['file']] = $item;
         }
     }
     //Resort the tree to ensure the found items are in the right spots
     $tree = ameMenu::sort_menu_tree($tree);
     //Order data is no longer necessary.
     $this->relative_template_order = null;
     return $tree;
 }