<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[BuddyPress][6503] trunk/bp-core: Introduces bp_nav_menu(), which displays a merged navigation list from registered components.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg > ul, #logmsg > ol { margin-left: 0; margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://buddypress.trac.wordpress.org/changeset/6503">6503</a></dd>
<dt>Author</dt> <dd>djpaul</dd>
<dt>Date</dt> <dd>2012-11-09 16:28:30 +0000 (Fri, 09 Nov 2012)</dd>
</dl>
<h3>Log Message</h3>
<pre>Introduces bp_nav_menu(), which displays a merged navigation list from registered components. Fixes <a href="http://buddypress.trac.wordpress.org/ticket/4629">#4629</a>.
BP has historically used bp_get_displayed_user_nav() and bp_get_options_nav() to render the navigation, and these menus have always been visually separate in the Default theme; either in columns before 1.2, and in rows since 1.2.
To provide more customisation for theme authors, and have both parts of the navigation displayed in the same block, this change introduces a merged menu function, with the parts from bp_get_options_nav nested appropriately under bp_get_displayed_user_nav.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkbpcorebpcoreclassesphp">trunk/bp-core/bp-core-classes.php</a></li>
<li><a href="#trunkbpcorebpcoretemplatephp">trunk/bp-core/bp-core-template.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkbpcorebpcoreclassesphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-core/bp-core-classes.php (6502 => 6503)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-core/bp-core-classes.php        2012-11-09 15:42:23 UTC (rev 6502)
+++ trunk/bp-core/bp-core-classes.php        2012-11-09 16:28:30 UTC (rev 6503)
</span><span class="lines">@@ -1796,3 +1796,155 @@
</span><span class="cx">                 return $this->maybe_make_link( $url );
</span><span class="cx">         }
</span><span class="cx"> }
</span><ins>+
+/**
+ * Create HTML list of BP nav items
+ *
+ * @since BuddyPress (1.7)
+ */
+class BP_Walker_Nav_Menu extends Walker_Nav_Menu {
+        /**
+         * @since BuddyPress (1.7)
+         * @var array
+         */
+        var $db_fields = array( 'id' => 'css_id', 'parent' => 'parent' );
+
+        /**
+         * @since BuddyPress (1.7)
+         * @var string
+         */
+        var $tree_type = array();
+
+        /**
+         * Display array of elements hierarchically.
+         *
+         * This method is almost identical to the version in {@link Walker::walk()}. The only change is on one line
+         * which has been commented. An IF was comparing 0 to a non-empty string which was preventing child elements
+         * being grouped under their parent menu element.
+         *
+         * This caused a problem for BuddyPress because our primary/secondary navigations doesn't have a unique numerical
+         * ID that describes a hierarchy (we use a slug). Obviously, WordPress Menus use Posts, and those have ID/post_parent.
+         *
+         * @param array $elements
+         * @param int $max_depth
+         * @return string
+         * @see Walker::walk()
+         * @since BuddyPress (1.7)
+         */
+        function walk( $elements, $max_depth ) {
+                $args = array_slice( func_get_args(), 2 );
+                $output = '';
+
+                if ( $max_depth < -1 ) // invalid parameter
+                        return $output;
+
+                if ( empty( $elements ) ) // nothing to walk
+                        return $output;
+
+                $id_field = $this->db_fields['id'];
+                $parent_field = $this->db_fields['parent'];
+
+                // flat display
+                if ( -1 == $max_depth ) {
+
+                        $empty_array = array();
+                        foreach ( $elements as $e )
+                                $this->display_element( $e, $empty_array, 1, 0, $args, $output );
+
+                        return $output;
+                }
+
+                /*
+                 * need to display in hierarchical order
+                 * separate elements into two buckets: top level and children elements
+                 * children_elements is two dimensional array, eg.
+                 * children_elements[10][] contains all sub-elements whose parent is 10.
+                 */
+                $top_level_elements = array();
+                $children_elements = array();
+
+                foreach ( $elements as $e ) {
+                        // BuddyPress: changed '==' to '==='. This is the only change from version in Walker::walk().
+                        if ( 0 === $e->$parent_field )
+                                $top_level_elements[] = $e;
+                        else
+                                $children_elements[$e->$parent_field][] = $e;
+                }
+
+                /*
+                 * when none of the elements is top level
+                 * assume the first one must be root of the sub elements
+                 */
+                if ( empty( $top_level_elements ) ) {
+
+                        $first = array_slice( $elements, 0, 1 );
+                        $root = $first[0];
+                        $top_level_elements = array();
+                        $children_elements = array();
+
+                        foreach ( $elements as $e ) {
+                                if ( $root->$parent_field == $e->$parent_field )
+                                        $top_level_elements[] = $e;
+                                else
+                                        $children_elements[$e->$parent_field][] = $e;
+                        }
+                }
+
+                foreach ( $top_level_elements as $e )
+                        $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
+
+                /*
+                 * if we are displaying all levels, and remaining children_elements is not empty,
+                 * then we got orphans, which should be displayed regardless
+                 */
+                if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
+                        $empty_array = array();
+
+                        foreach ( $children_elements as $orphans )
+                                foreach ( $orphans as $op )
+                                        $this->display_element( $op, $empty_array, 1, 0, $args, $output );
+                 }
+
+                 return $output;
+        }
+
+        /**
+         * Displays the current <li> that we are on.
+         *
+         * @param string $output Passed by reference. Used to append additional content.
+         * @param object $item Menu item data object.
+         * @param int $depth Depth of menu item. Used for padding. Optional, defaults to 0.
+         * @param array $args Optional
+         * @param int $current_page Menu item ID. Optional.
+         * @since BuddyPress (1.7)
+         */
+        function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
+                // If we're someway down the tree, indent the HTML with the appropriate number of tabs
+                $indent = $depth ? str_repeat( "\t", $depth ) : '';
+
+                // Add HTML classes
+                $class_names = join( ' ', apply_filters( 'bp_nav_menu_css_class', array_filter( $item->class ), $item, $args ) );
+                $class_names = ! empty( $class_names ) ? ' class="' . esc_attr( $class_names ) . '"' : '';
+
+                // Add HTML ID
+                $id = sanitize_html_class( $item->css_id . '-personal-li' ); // Backpat with BP pre-1.7
+                $id = apply_filters( 'bp_nav_menu_item_id', $id, $item, $args );
+                $id = ! empty( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';
+
+                // Opening tag; closing tag is handled in Walker_Nav_Menu::end_el().
+                $output .= $indent . '<li' . $id . $class_names . '>';
+
+                // Add href attribute
+                $attributes = ! empty( $item->link ) ? ' href="' . esc_attr( esc_url( $item->link ) ) . '"' : '';
+
+                // Construct the link
+                $item_output = $args->before;
+                $item_output .= '<a' . $attributes . '>';
+                $item_output .= $args->link_before . apply_filters( 'the_title', $item->name, 0 ) . $args->link_after;
+                $item_output .= '</a>';
+                $item_output .= $args->after;
+
+                // $output is byref
+                $output .= apply_filters( 'bp_walker_nav_menu_start_el', $item_output, $item, $depth, $args );
+        }
+}
</ins></span></pre></div>
<a id="trunkbpcorebpcoretemplatephp"></a>
<div class="modfile"><h4>Modified: trunk/bp-core/bp-core-template.php (6502 => 6503)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-core/bp-core-template.php        2012-11-09 15:42:23 UTC (rev 6502)
+++ trunk/bp-core/bp-core-template.php        2012-11-09 16:28:30 UTC (rev 6503)
</span><span class="lines">@@ -1742,3 +1742,211 @@
</span><span class="cx">                 return apply_filters( 'bp_get_the_body_class', $classes, $bp_classes, $wp_classes, $custom_classes );
</span><span class="cx">         }
</span><span class="cx">         add_filter( 'body_class', 'bp_get_the_body_class', 10, 2 );
</span><ins>+
+/**
+ * Sort BuddyPress nav menu items by their position property.
+ *
+ * This is an internal convenience function and it will probably be removed in a later release. Do not use.
+ *
+ * @access private
+ * @param array $a First item
+ * @param array $b Second item
+ * @return int Returns an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.
+ * @since BuddyPress (1.7)
+ */
+function _bp_nav_menu_sort( $a, $b ) {
+        if ( $a["position"] == $b["position"] )
+                return 0;
+
+        else if ( $a["position"] < $b["position"] )
+                return -1;
+
+        else
+                return 1;
+}
+
+/**
+ * Get an array of all the items registered in the primary and secondary BuddyPress navigation menus
+ *
+ * @return array
+ * @since BuddyPress (1.7)
+ */
+function bp_get_nav_menu_items() {
+        $menus = $selected_menus = array();
+
+        // Get the second level menus
+        foreach ( (array) buddypress()->bp_options_nav as $parent_menu => $sub_menus ) {
+
+                // The root menu's ID is "xprofile", but the Profile submenus are using "profile". See BP_Core::setup_nav().
+                if ( 'profile' == $parent_menu )
+                        $parent_menu = 'xprofile';
+
+                // Sort the items in this menu's navigation by their position property
+                $second_level_menus = (array) $sub_menus;
+                usort( $second_level_menus, '_bp_nav_menu_sort' );
+
+                // Iterate through the second level menus
+                foreach( $second_level_menus as $sub_nav ) {
+
+                        // Skip items we don't have access to
+                        if ( ! $sub_nav['user_has_access'] )
+                                continue;
+
+                        // Add this menu
+                        $menu = new stdClass;
+                        $menu->class = array();
+                        $menu->css_id = $sub_nav['css_id'];
+                        $menu->link = $sub_nav['link'];
+                        $menu->name = $sub_nav['name'];
+                        $menu->parent = $parent_menu; // Associate this sub nav with a top-level menu
+
+                        // If we're viewing this item's screen, record that we need to mark its parent menu to be selected
+                        if ( $sub_nav['slug'] == bp_current_action() ) {
+                                $menu->class = array( 'current-menu-item' );
+                                $selected_menus[] = $parent_menu;
+                        }
+
+                        $menus[] = $menu;
+                }
+        }
+
+        // Get the top-level menu parts (Friends, Groups, etc) and sort by their position property
+        $top_level_menus = (array) buddypress()->bp_nav;
+        usort( $top_level_menus, '_bp_nav_menu_sort' );
+
+        // Iterate through the top-level menus
+        foreach ( $top_level_menus as $nav ) {
+
+                // Skip items marked as user-specific if you're not on your own profile
+                if ( ! $nav['show_for_displayed_user'] && ! bp_is_my_profile() )
+                        continue;
+
+                // Get the correct menu link. See http://buddypress.trac.wordpress.org/ticket/4624
+                $link = bp_loggedin_user_domain() ? str_replace( bp_loggedin_user_domain(), bp_displayed_user_domain(), $nav['link'] ) : trailingslashit( bp_displayed_user_domain() . $nav['link'] );
+
+                // Add this menu
+                $menu = new stdClass;
+                $menu->class = array();
+                $menu->css_id = $nav['css_id'];
+                $menu->link = $link;
+                $menu->name = $nav['name'];
+                $menu->parent = 0;
+
+                // Check if we need to mark this menu as selected
+                if ( in_array( $nav['css_id'], $selected_menus ) )
+                        $menu->class = array( 'current-menu-parent' );
+
+                $menus[] = $menu;
+        }
+
+        return apply_filters( 'bp_get_nav_menu_items', $menus );
+}
+
+/**
+ * Displays a navigation menu.
+ *
+ * @param string|array $args Optional arguments:
+ * before - Text before the link text.
+ * container - Whether to wrap the ul, and what to wrap it with. Defaults to div.
+ * container_class - The class that is applied to the container. Defaults to 'menu-bp-container'.
+ * container_id - The ID that is applied to the container. Defaults to blank.
+ * depth - How many levels of the hierarchy are to be included. 0 means all. Defaults to 0.
+ * echo - Whether to echo the menu or return it. Defaults to echo.
+ * fallback_cb - If the menu doesn't exists, a callback function will fire. Defaults to false (no fallback).
+ * items_wrap - How the list items should be wrapped. Defaults to a ul with an id and class. Uses printf() format with numbered placeholders.
+ * link_after - Text after the link.
+ * link_before - Text before the link.
+ * menu_class - CSS class to use for the ul element which forms the menu. Defaults to 'menu'.
+ * menu_id - The ID that is applied to the ul element which forms the menu. Defaults to 'menu-bp', incremented.
+ * walker - Allows a custom walker to be specified. Defaults to 'BP_Walker_Nav_Menu'.
+ * @since BuddyPress (1.7)
+ */
+function bp_nav_menu( $args = array() ) {
+        static $menu_id_slugs = array();
+
+        $defaults = array(
+                'after' => '',
+                'before' => '',
+                'container' => 'div',
+                'container_class' => '',
+                'container_id' => '',
+                'depth' => 0,
+                'echo' => true,
+                'fallback_cb' => false,
+                'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>',
+                'link_after' => '',
+                'link_before' => '',
+                'menu_class' => 'menu',
+                'menu_id' => '',
+                'walker' => '',
+        );
+        $args = wp_parse_args( $args, $defaults );
+        $args = apply_filters( 'bp_nav_menu_args', $args );
+        $args = (object) $args;
+
+        $items = $nav_menu = '';
+        $show_container = false;
+
+        // Create custom walker if one wasn't set
+        if ( empty( $args->walker ) )
+                $args->walker = new BP_Walker_Nav_Menu;
+
+        // Sanitise values for class and ID
+        $args->container_class = sanitize_html_class( $args->container_class );
+        $args->container_id = sanitize_html_class( $args->container_id );
+
+        // Whether to wrap the ul, and what to wrap it with
+        if ( $args->container ) {
+                $allowed_tags = apply_filters( 'wp_nav_menu_container_allowedtags', array( 'div', 'nav', ) );
+
+                if ( in_array( $args->container, $allowed_tags ) ) {
+                        $show_container = true;
+
+                        $class = $args->container_class ? ' class="' . esc_attr( $args->container_class ) . '"' : ' class="menu-bp-container"';
+                        $id = $args->container_id ? ' id="' . esc_attr( $args->container_id ) . '"' : '';
+                        $nav_menu .= '<' . $args->container . $id . $class . '>';
+                }
+        }
+
+        // Get the BuddyPress menu items
+        $menu_items = apply_filters( 'bp_nav_menu_objects', bp_get_nav_menu_items(), $args );
+        $items = walk_nav_menu_tree( $menu_items, $args->depth, $args );
+        unset( $menu_items );
+
+        // Set the ID that is applied to the ul element which forms the menu.
+        if ( ! empty( $args->menu_id ) ) {
+                $wrap_id = $args->menu_id;
+
+        } else {
+                $wrap_id = 'menu-bp';
+
+                // If a specific ID wasn't requested, and there are multiple menus on the same screen, make sure the autogenerated ID is unique
+                while ( in_array( $wrap_id, $menu_id_slugs ) ) {
+                        if ( preg_match( '#-(\d+)$#', $wrap_id, $matches ) )
+                                $wrap_id = preg_replace('#-(\d+)$#', '-' . ++$matches[1], $wrap_id );
+                        else
+                                $wrap_id = $wrap_id . '-1';
+                }
+        }
+        $menu_id_slugs[] = $wrap_id;
+
+        // Allow plugins to hook into the menu to add their own <li>'s
+        $items = apply_filters( 'bp_nav_menu_items', $items, $args );
+
+        // Build the output
+        $wrap_class = $args->menu_class ? $args->menu_class : '';
+        $nav_menu .= sprintf( $args->items_wrap, esc_attr( $wrap_id ), esc_attr( $wrap_class ), $items );
+        unset( $items );
+
+        // If we've wrapped the ul, close it
+        if ( $show_container )
+                $nav_menu .= '</' . $args->container . '>';
+
+        // Final chance to modify output
+        $nav_menu = apply_filters( 'bp_nav_menu', $nav_menu, $args );
+
+        if ( $args->echo )
+                echo $nav_menu;
+        else
+                return $nav_menu;
+}
</ins></span></pre>
</div>
</div>
</body>
</html>