aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engine/classes/ElggMenuBuilder.php249
-rw-r--r--engine/classes/ElggMenuItem.php329
-rw-r--r--engine/lib/navigation.php64
-rw-r--r--engine/lib/views.php37
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