diff options
Diffstat (limited to 'engine/classes/ElggMenuBuilder.php')
| -rw-r--r-- | engine/classes/ElggMenuBuilder.php | 291 | 
1 files changed, 291 insertions, 0 deletions
| diff --git a/engine/classes/ElggMenuBuilder.php b/engine/classes/ElggMenuBuilder.php new file mode 100644 index 000000000..b463143d8 --- /dev/null +++ b/engine/classes/ElggMenuBuilder.php @@ -0,0 +1,291 @@ +<?php +/** + * Elgg Menu Builder + * + * @package    Elgg.Core + * @subpackage Navigation + * @since      1.8.0 + */ +class ElggMenuBuilder { + +	/** +	 * @var ElggMenuItem[] +	 */ +	protected $menu = array(); + +	protected $selected = null; + +	/** +	 * ElggMenuBuilder constructor +	 * +	 * @param ElggMenuItem[] $menu Array of ElggMenuItem objects +	 */ +	public function __construct(array $menu) { +		$this->menu = $menu; +	} + +	/** +	 * Get a prepared menu array +	 * +	 * @param mixed $sort_by Method to sort the menu by. @see ElggMenuBuilder::sort() +	 * @return array +	 */ +	public function getMenu($sort_by = 'text') { + +		$this->selectFromContext(); + +		$this->selected = $this->findSelected(); + +		$this->setupSections(); + +		$this->setupTrees(); + +		$this->sort($sort_by); + +		return $this->menu; +	} + +	/** +	 * Get the selected menu item +	 * +	 * @return ElggMenuItem +	 */ +	public function getSelected() { +		return $this->selected; +	} + +	/** +	 * Select menu items for the current context +	 * +	 * @return void +	 */ +	protected function selectFromContext() { +		if (!isset($this->menu)) { +			$this->menu = array(); +			return; +		} + +		// get menu items for this context +		$selected_menu = array(); +		foreach ($this->menu as $menu_item) { +			if (!is_object($menu_item)) { +				elgg_log("A non-object was passed to ElggMenuBuilder", "ERROR"); +				continue; +			} +			if ($menu_item->inContext()) { +				$selected_menu[] = $menu_item; +			} +		} + +		$this->menu = $selected_menu; +	} + +	/** +	 * Group the menu items into sections +	 *  +	 * @return void +	 */ +	protected function setupSections() { +		$sectioned_menu = array(); +		foreach ($this->menu as $menu_item) { +			if (!isset($sectioned_menu[$menu_item->getSection()])) { +				$sectioned_menu[$menu_item->getSection()] = array(); +			} +			$sectioned_menu[$menu_item->getSection()][] = $menu_item; +		} +		$this->menu = $sectioned_menu; +	} + +	/** +	 * Create trees for each menu section +	 * +	 * @internal The tree is doubly linked (parent and children links) +	 * @return void +	 */ +	protected function setupTrees() { +		$menu_tree = array(); + +		foreach ($this->menu as $key => $section) { +			$parents = array(); +			$children = array(); +			// divide base nodes from children +			foreach ($section as $menu_item) { +				/* @var ElggMenuItem $menu_item */ +				$parent_name = $menu_item->getParentName(); +				if (!$parent_name) { +					$parents[$menu_item->getName()] = $menu_item; +				} else { +					$children[] = $menu_item; +				} +			} + +			// attach children to parents +			$iteration = 0; +			$current_gen = $parents; +			$next_gen = null; +			while (count($children) && $iteration < 5) { +				foreach ($children as $index => $menu_item) { +					$parent_name = $menu_item->getParentName(); +					if (array_key_exists($parent_name, $current_gen)) { +						$next_gen[$menu_item->getName()] = $menu_item; +						if (!in_array($menu_item, $current_gen[$parent_name]->getData('children'))) { +							$current_gen[$parent_name]->addChild($menu_item); +							$menu_item->setParent($current_gen[$parent_name]); +						} +						unset($children[$index]); +					} +				} +				$current_gen = $next_gen; +				$iteration += 1; +			} + +			// convert keys to indexes for first level of tree +			$parents = array_values($parents); + +			$menu_tree[$key] = $parents; +		} + +		$this->menu = $menu_tree; +	} + +	/** +	 * Find the menu item that is currently selected +	 * +	 * @return ElggMenuItem +	 */ +	protected function findSelected() { + +		// do we have a selected menu item already +		foreach ($this->menu as $menu_item) { +			if ($menu_item->getSelected()) { +				return $menu_item; +			} +		} + +		// scan looking for a selected item +		foreach ($this->menu as $menu_item) { +			if ($menu_item->getHref()) { +				if (elgg_http_url_is_identical(current_page_url(), $menu_item->getHref())) { +					$menu_item->setSelected(true); +					return $menu_item; +				} +			} +		} + +		return null; +	} + +	/** +	 * Sort the menu sections and trees +	 * +	 * @param mixed $sort_by Sort type as string or php callback +	 * @return void +	 */ +	protected function sort($sort_by) { + +		// sort sections +		ksort($this->menu); + +		switch ($sort_by) { +			case 'text': +				$sort_callback = array('ElggMenuBuilder', 'compareByText'); +				break; +			case 'name': +				$sort_callback = array('ElggMenuBuilder', 'compareByName'); +				break; +			case 'priority': +				$sort_callback = array('ElggMenuBuilder', 'compareByWeight'); +				break; +			case 'register': +				// use registration order - usort breaks this +				return; +				break; +			default: +				if (is_callable($sort_by)) { +					$sort_callback = $sort_by; +				} else { +					return; +				} +				break; +		} + +		// sort each section +		foreach ($this->menu as $index => $section) { +			foreach ($section as $key => $node) { +				$section[$key]->setData('original_order', $key); +			} +			usort($section, $sort_callback); +			$this->menu[$index] = $section; + +			// depth first traversal of tree +			foreach ($section as $root) { +				$stack = array(); +				array_push($stack, $root); +				while (!empty($stack)) { +					$node = array_pop($stack); +					/* @var ElggMenuItem $node */ +					$node->sortChildren($sort_callback); +					$children = $node->getChildren(); +					if ($children) { +						$stack = array_merge($stack, $children); +					} +				} +			} +		} +	} + +	/** +	 * Compare two menu items by their display text +	 * +	 * @param ElggMenuItem $a Menu item +	 * @param ElggMenuItem $b Menu item +	 * @return bool +	 */ +	public static function compareByText($a, $b) { +		$at = $a->getText(); +		$bt = $b->getText(); + +		$result = strnatcmp($at, $bt); +		if ($result === 0) { +			return $a->getData('original_order') - $b->getData('original_order'); +		} +		return $result; +	} + +	/** +	 * Compare two menu items by their identifiers +	 * +	 * @param ElggMenuItem $a Menu item +	 * @param ElggMenuItem $b Menu item +	 * @return bool +	 */ +	public static function compareByName($a, $b) { +		$an = $a->getName(); +		$bn = $b->getName(); + +		$result = strcmp($an, $bn); +		if ($result === 0) { +			return $a->getData('original_order') - $b->getData('original_order'); +		} +		return $result; +	} + +	/** +	 * Compare two menu items by their priority +	 * +	 * @param ElggMenuItem $a Menu item +	 * @param ElggMenuItem $b Menu item +	 * @return bool +	 * +	 * @todo change name to compareByPriority +	 */ +	public static function compareByWeight($a, $b) { +		$aw = $a->getWeight(); +		$bw = $b->getWeight(); + +		if ($aw == $bw) { +			return $a->getData('original_order') - $b->getData('original_order'); +		} +		return $aw - $bw; +	} +} | 
