diff options
Diffstat (limited to 'engine')
-rw-r--r-- | engine/classes/ElggMenuBuilder.php | 249 | ||||
-rw-r--r-- | engine/classes/ElggMenuItem.php | 329 | ||||
-rw-r--r-- | engine/lib/navigation.php | 64 | ||||
-rw-r--r-- | engine/lib/views.php | 37 |
4 files changed, 679 insertions, 0 deletions
diff --git a/engine/classes/ElggMenuBuilder.php b/engine/classes/ElggMenuBuilder.php new file mode 100644 index 000000000..b57ea6e18 --- /dev/null +++ b/engine/classes/ElggMenuBuilder.php @@ -0,0 +1,249 @@ +<?php +/** + * Elgg Menu Builder + * + * @package Elgg.Core + * @subpackage Navigation + * + * @since 1.8.0 + */ +class ElggMenuBuilder { + + protected $menu = array(); + + protected $selected = null; + + /** + * ElggMenuBuilder constructor + * + * @param string $name Identifier of the menu + */ + public function __construct($name) { + global $CONFIG; + + $this->menu = $CONFIG->menus[$name]; + } + + /** + * Get a prepared menu array + * + * @param mixed $sort_by + * @return array + */ + public function getMenu($sort_by) { + + $this->selectFromContext(); + + $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 ($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) { + $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; + 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; + $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->getURL()) { + if (elgg_http_url_is_identical(full_url(), $menu_item->getURL())) { + $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 'title': + $sort_callback = array('ElggMenuBuilder', 'compareByTitle'); + break; + case 'name'; + $sort_callback = array('ElggMenuBuilder', 'compareByName'); + break; + case 'order': + // use registration order + return; + break; + default: + if (is_callable($sort_by)) { + $sort_callback = $sort_by; + } else { + return; + } + break; + } + + // sort each section + foreach ($this->menu as $index => $section) { + 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); + $node->sortChildren($sort_callback); + $children = $node->getChildren(); + if ($children) { + $stack = array_merge($stack, $children); + } + $p = count($stack); + } + } + } + } + + /** + * Compare two menu items by their titles + * + * @param ElggMenuItem $a + * @param ElggMenuItem $b + * @return bool + */ + public static function compareByTitle($a, $b) { + $a = $a->getTitle(); + $b = $b->getTitle(); + + return strnatcmp($a, $b); + } + + /** + * Compare two menu items by their identifiers + * + * @param ElggMenuItem $a + * @param ElggMenuItem $b + * @return bool + */ + public static function compareByName($a, $b) { + $a = $a->getName(); + $b = $b->getName(); + + return strcmp($a, $b); + } +} diff --git a/engine/classes/ElggMenuItem.php b/engine/classes/ElggMenuItem.php new file mode 100644 index 000000000..97dabe62a --- /dev/null +++ b/engine/classes/ElggMenuItem.php @@ -0,0 +1,329 @@ +<?php +/** + * Elgg Menu Item + * + * @package Elgg.Core + * @subpackage Navigation + * + * @since 1.8.0 + */ +class ElggMenuItem { + /** + * @var string Identifier of the menu + */ + protected $name; + + /** + * @var string The menu display string + */ + protected $title; + + /** + * @var string The menu url + */ + protected $url; + + /** + * @var array Page context array + */ + protected $contexts = array('all'); + + /** + * @var string Menu section identifier + */ + protected $section = 'default'; + + /** + * @var string Tooltip + */ + protected $tooltip = ''; + + /** + * @var bool Is this the currently selected menu item + */ + protected $selected = false; + + /** + * @var string Identifier of this item's parent + */ + protected $parent_name = ''; + + /** + * @var ElggMenuItem The parent object or null + */ + protected $parent = null; + + /** + * @var array Array of children objects or empty array + */ + protected $children = array(); + + /** + * ElggMenuItem constructor + * + * @param string $name Identifier of the menu item + * @param string $title Title of the menu item + * @param string $url URL of the menu item + */ + public function __construct($name, $title, $url) { + $this->name = $name; + $this->title = $title; + $this->url = $url; + } + + /** + * ElggMenuItem factory method + * + * This static method creates an ElggMenuItem from an associative array. + * Required keys are name, title, and url. + * + * @param array $options Option array of key value pairs + * + * @return ElggMenuItem or NULL on error + */ + public static function factory($options) { + if (!isset($options['name']) || !isset($options['title']) || !isset($options['url'])) { + return NULL; + } + + $item = new ElggMenuItem($options['name'], $options['title'], $options['url']); + + // special catch in case someone uses context rather than contexts + if (isset($options['context'])) { + $options['contexts'] = $options['context']; + unset($options['context']); + } + + foreach ($options as $key => $value) { + $item->$key = $value; + } + + // make sure contexts is set correctly + if (isset($options['contexts'])) { + $item->setContext($options['contexts']); + } + + return $item; + } + + /** + * Get the identifier of the menu item + * + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Get the display title of the menu + * + * @return string + */ + public function getTitle() { + return $this->title; + } + + /** + * Get the URL of the menu item + * + * @return string + */ + public function getURL() { + return $this->url; + } + + /** + * Set the contexts that this menu item is available for + * + * @param array $contexts An array of context strings + * + * @return void + */ + public function setContext($contexts) { + if (is_string($contexts)) { + $contexts = array($contexts); + } + $this->contexts = $contexts; + } + + /** + * Get an array of context strings + * + * @return array + */ + public function getContext() { + return $this->contexts; + } + + /** + * Should this menu item be used given the current context + * + * @param string $context A context string (default is empty string for + * current context stack. + * + * @return bool + */ + public function inContext($context = '') { + if ($context) { + return in_array($context, $this->contexts); + } + + if (in_array('all', $this->contexts)) { + return true; + } + + foreach ($this->contexts as $context) { + if (elgg_in_context($context)) { + return true; + } + } + return false; + } + + /** + * Set the selected flag + * + * @param bool $state Selected state (default is true) + * + * @return void + */ + public function setSelected($state = true) { + $this->selected = $state; + } + + /** + * Get selected state + * + * @return bool + */ + public function getSelected() { + return $this->selected; + } + + /** + * Set the tool tip text + * + * @param string $text The text of the tool tip + * + * @return void + */ + public function setTooltip($text) { + $this->tooltip = $text; + } + + /** + * Get the tool tip text + * + * @return string + */ + public function getTooltip() { + return $this->tooltip; + } + + /** + * Set the section identifier + * + * @param string $section The identifier of the section + * + * @return void + */ + public function setSection($section) { + $this->section = $section; + } + + /** + * Get the section identifier + * + * @return string + */ + public function getSection() { + return $this->section; + } + + /** + * Set the parent identifier + * + * @param string $parent_name The identifier of the parent ElggMenuItem + * + * @return void + */ + public function setParentName($parent_name) { + $this->parent_name = $parent_name; + } + + /** + * Get the parent identifier + * + * @return string + */ + public function getParentName() { + return $this->parent_name; + } + + /** + * Set the parent menu item + * + * @param ElggMenuItem $parent + * + * @return void + */ + public function setParent($parent) { + $this->parent = $parent; + } + + /** + * Get the parent menu item + * + * @return ElggMenuItem or null + */ + public function getParent() { + return $this->parent; + } + + /** + * Add a child menu item + * + * @param ElggMenuItem $item + * + * @return void + */ + public function addChild($item) { + $this->children[] = $item; + } + + /** + * Get the children menu items + * + * @return array + */ + public function getChildren() { + return $this->children; + } + + /** + * Sort the children + * + * @param string $sort_function + * + * @return void + */ + public function sortChildren($sort_function) { + usort($this->children, $sort_function); + } + + /** + * Get the menu link + * + * @todo add styling + * + * @return string + */ + public function getLink() { + $vars = array( + 'href' => $this->url, + 'text' => $this->title + ); + return elgg_view('output/url', $vars); + } +} diff --git a/engine/lib/navigation.php b/engine/lib/navigation.php index 75c5958f4..7b8c32f02 100644 --- a/engine/lib/navigation.php +++ b/engine/lib/navigation.php @@ -8,6 +8,70 @@ */ /** + * Register an item for an Elgg menu + * + * @param string $menu_name The name of the menu: site, page, userhover, + * userprofile, groupprofile, or any custom menu + * @param mixed $menu_item A ElggMenuItem object or an array of options in format: + * name => STR Menu item identifier (required) + * title => STR Menu item title (required) + * url => STR Menu item URL (required) + * contexts => ARR Page context strings + * section => STR Menu section identifier + * tooltip => STR Menu item tooltip + * selected => BOOL Is this menu item currently selected + * parent_name => STR Identifier of the parent menu item + * + * Custom options can be added as key value pairs. + * + * @return bool + * @since 1.8.0 + */ +function elgg_register_menu_item($menu_name, $menu_item) { + global $CONFIG; + + if (!isset($CONFIG->menus[$menu_name])) { + $CONFIG->menus[$menu_name] = array(); + } + + if (is_array($menu_item)) { + $menu_item = ElggMenuItem::factory($menu_item); + if (!$menu_item) { + return false; + } + } + + $CONFIG->menus[$menu_name][] = $menu_item; + return true; +} + +/** + * Remove an item from a menu + * + * @param string $menu_name The name of the menu + * @param string $item_name The unique identifier for this menu item + * + * @return bool + * @since 1.8.0 + */ +function elgg_unregister_menu_item($menu_name, $item_name) { + global $CONFIG; + + if (!isset($CONFIG->menus[$menu_name])) { + return false; + } + + foreach ($CONFIG->menus[$menu_name] as $index => $menu_object) { + if ($menu_object->name == $item_name) { + unset($CONFIG->menus[$menu_name][$index]); + return true; + } + } + + return false; +} + +/** * Deprecated by elgg_add_submenu_item() * * @see elgg_add_submenu_item() diff --git a/engine/lib/views.php b/engine/lib/views.php index ade5ff678..71922cb6c 100644 --- a/engine/lib/views.php +++ b/engine/lib/views.php @@ -644,6 +644,43 @@ function elgg_view_layout($layout_name, $vars = array()) { } /** + * Render a menu + * + * @param string $menu_name The name of the menu + * @param array $vars An associative array of display options for the menu. + * Options include: + * sort_by => string or php callback + * string options: 'name', 'title' (default), 'order' (registration order) + * php callback: a compare function for usort + * + * @return string + * @since 1.8.0 + */ +function elgg_view_menu($menu_name, array $vars = array()) { + + $vars['name'] = $menu_name; + + $sort_by = elgg_get_array_value('sort_by', $vars, 'title'); + + // Give plugins a chance to add menu items just before creation. + // This supports context sensitive menus (ex. user hover). + elgg_trigger_plugin_hook('register', "menu:$menu_name", $vars, NULL); + + $builder = new ElggMenuBuilder($menu_name); + $vars['menu'] = $builder->getMenu($sort_by); + $vars['selected_item'] = $builder->getSelected(); + + // Let plugins modify the menu + $vars['menu'] = elgg_trigger_plugin_hook('prepare', "menu:$menu_name", $vars, $vars['menu']); + + if (elgg_view_exists("navigation/menu/$menu_name")) { + return elgg_view("navigation/menu/$menu_name", $vars); + } else { + return elgg_view("navigation/menu/default", $vars); + } +} + +/** * Returns a string of a rendered entity. * * Entity views are either determined by setting the view property on the entity |