Skip to:
Content

BuddyPress.org


Ignore:
Timestamp:
05/12/2016 05:19:06 PM (8 years ago)
Author:
boonebgorges
Message:

Introduce new API for BuddyPress navigation.

The new BP_Core_Nav overhauls the way that BuddyPress registers, stores, and
renders navigation items. Navigations are now component-specific, eliminating
the potential for confusion and conflict between navigation items with similar
names in different components, and opening the possibility of generating navs
for separate objects of the same type on a single pageload.

The new nav API replaces the old bp_nav and bp_options_nav system, which
dates from the earliest days of BuddyPress. These global properties were
responsible for handling nav and subnav across all of BP's components. The data
structure of bp_nav and bp_options_nav was simultaneously too opaque (in the
sense of being difficult to approach for developers and not having a complete
interface for programmatic modification) and too transparent (forcing devs to
manipulate the global arrays directly in order to customize navigation). The
new system eliminates most of these problems, by removing direct access to the
underlying navigation data, while providing a full-fledged API for accessing
and modifying that data.

An abstraction layer provides backward compatibility for most legacy uses of
bp_nav and bp_options_nav. Plugins that read data from the globals, modify
the data (eg $bp->bp_nav['foo']['name'] = 'Bar'), and unset nav items via
bp_nav and bp_options_nav should all continue to work as before. Anyone
accessing the globals in this way will see a _doing_it_wrong() notice. This
backward compatibility layer requires SPL (Standard PHP Library), which means
that it will not be enabled on certain configurations running PHP 5.2.x. (SPL
cannot be disabled in PHP 5.3+, and is on by default for earlier versions.)

The new system breaks backward compatibility in a number of small ways. Our
research suggests that these breaks will affect very few customizations, but
we list them here for posterity:

  • Some array functions, such as sort(), will no longer work to modify the nav globals.
  • Subnav items added to nonexistent parents can no longer be successfully registered. Previously, they could be loaded into the global, but were never displayed on the front end.
  • Manual management of group navigation items previously worked by passing the group slug as the parent_slug parameter to the bp_core_*_subnav_item() functions. The new API requires specifying a $component ('members', 'groups') when accessing nav items. To provide compatibility with legacy use - where $component was not required - we make some educated guesses about whether a nav item is "meant" to be attached to a group. This could result in unpredictable behavior in cases where a group slug clashes with a Members navigation item. This has always been broken - see #5103 - but may break in different ways after this changeset.

Props imath, boonebgorges, r-a-y.
Fixes #5103. Fixes #6534.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/bp-core/bp-core-template.php

    r10693 r10745  
    1414 * Output the "options nav", the secondary-level single item navigation menu.
    1515 *
    16  * Uses the $bp->bp_options_nav global to render out the sub navigation for the
     16 * Uses the component's nav global to render out the sub navigation for the
    1717 * current component. Each component adds to its sub navigation array within
    1818 * its own setup_nav() function.
     
    4141    $selected_item   = bp_current_action();
    4242
     43    // Default to the Members nav.
    4344    if ( ! bp_is_single_item() ) {
    44         if ( !isset( $bp->bp_options_nav[$component_index] ) || count( $bp->bp_options_nav[$component_index] ) < 1 ) {
     45        // Set the parent slug, if not provided.
     46        if ( empty( $parent_slug ) ) {
     47            $parent_slug = $component_index;
     48        }
     49
     50        $secondary_nav_items = $bp->members->nav->get_secondary( array( 'parent_slug' => $parent_slug ) );
     51
     52        if ( ! $secondary_nav_items ) {
    4553            return false;
    46         } else {
    47             $the_index = $component_index;
    48         }
     54        }
     55
     56    // For a single item, try to use the component's nav.
    4957    } else {
    5058        $current_item = bp_current_item();
    51 
     59        $single_item_component = bp_current_component();
     60
     61        // Adjust the selected nav item for the current single item if needed.
    5262        if ( ! empty( $parent_slug ) ) {
    5363            $current_item  = $parent_slug;
     
    5565        }
    5666
    57         if ( !isset( $bp->bp_options_nav[$current_item] ) || count( $bp->bp_options_nav[$current_item] ) < 1 ) {
     67        // If the nav is not defined by the parent component, look in the Members nav.
     68        if ( ! isset( $bp->{$single_item_component}->nav ) ) {
     69            $secondary_nav_items = $bp->members->nav->get_secondary( array( 'parent_slug' => $current_item ) );
     70        } else {
     71            $secondary_nav_items = $bp->{$single_item_component}->nav->get_secondary( array( 'parent_slug' => $current_item ) );
     72        }
     73
     74        if ( ! $secondary_nav_items ) {
    5875            return false;
    59         } else {
    60             $the_index = $current_item;
    6176        }
    6277    }
    6378
    6479    // Loop through each navigation item.
    65     foreach ( (array) $bp->bp_options_nav[$the_index] as $subnav_item ) {
    66         if ( empty( $subnav_item['user_has_access'] ) ) {
     80    foreach ( $secondary_nav_items as $subnav_item ) {
     81        if ( empty( $subnav_item->user_has_access ) ) {
    6782            continue;
    6883        }
    6984
    7085        // If the current action or an action variable matches the nav item id, then add a highlight CSS class.
    71         if ( $subnav_item['slug'] == $selected_item ) {
     86        if ( $subnav_item->slug === $selected_item ) {
    7287            $selected = ' class="current selected"';
    7388        } else {
     
    89104         * @param string $selected_item Current action.
    90105         */
    91         echo apply_filters( 'bp_get_options_nav_' . $subnav_item['css_id'], '<li id="' . esc_attr( $subnav_item['css_id'] . '-' . $list_type . '-li' ) . '" ' . $selected . '><a id="' . esc_attr( $subnav_item['css_id'] ) . '" href="' . esc_url( $subnav_item['link'] ) . '">' . $subnav_item['name'] . '</a></li>', $subnav_item, $selected_item );
     106        echo apply_filters( 'bp_get_options_nav_' . $subnav_item->css_id, '<li id="' . esc_attr( $subnav_item->css_id . '-' . $list_type . '-li' ) . '" ' . $selected . '><a id="' . esc_attr( $subnav_item->css_id ) . '" href="' . esc_url( $subnav_item->link ) . '">' . $subnav_item->name . '</a></li>', $subnav_item, $selected_item );
    92107    }
    93108}
     
    30113026        $component_subnav_name = '';
    30123027
     3028        if ( ! empty( $bp->members->nav ) ) {
     3029            $primary_nav_item = $bp->members->nav->get_primary( array( 'slug' => $component_id ), false );
     3030            $primary_nav_item = reset( $primary_nav_item );
     3031        }
     3032
    30133033        // Use the component nav name.
    3014         if ( ! empty( $bp->bp_nav[$component_id] ) ) {
    3015             $component_name = _bp_strip_spans_from_title( $bp->bp_nav[ $component_id ]['name'] );
     3034        if ( ! empty( $primary_nav_item->name ) ) {
     3035            $component_name = _bp_strip_spans_from_title( $primary_nav_item->name );
    30163036
    30173037        // Fall back on the component ID.
     
    30203040        }
    30213041
     3042        if ( ! empty( $bp->members->nav ) ) {
     3043            $secondary_nav_item = $bp->members->nav->get_secondary( array(
     3044                'parent_slug' => $component_id,
     3045                'slug'        => bp_current_action()
     3046            ), false );
     3047
     3048            if ( $secondary_nav_item ) {
     3049                $secondary_nav_item = reset( $secondary_nav_item );
     3050            }
     3051        }
     3052
    30223053        // Append action name if we're on a member component sub-page.
    3023         if ( ! empty( $bp->bp_options_nav[ $component_id ] ) && ! empty( $bp->canonical_stack['action'] ) ) {
    3024             $component_subnav_name = wp_filter_object_list( $bp->bp_options_nav[ $component_id ], array( 'slug' => bp_current_action() ), 'and', 'name' );
    3025 
    3026             if ( ! empty( $component_subnav_name ) ) {
    3027                 $component_subnav_name = array_shift( $component_subnav_name );
    3028             }
     3054        if ( ! empty( $secondary_nav_item->name ) && ! empty( $bp->canonical_stack['action'] ) ) {
     3055            $component_subnav_name = $secondary_nav_item->name;
    30293056        }
    30303057
     
    30463073        }
    30473074
    3048     // A single group.
    3049     } elseif ( bp_is_active( 'groups' ) && ! empty( $bp->groups->current_group ) && ! empty( $bp->bp_options_nav[ $bp->groups->current_group->slug ] ) ) {
    3050         $subnav      = isset( $bp->bp_options_nav[ $bp->groups->current_group->slug ][ bp_current_action() ]['name'] ) ? $bp->bp_options_nav[ $bp->groups->current_group->slug ][ bp_current_action() ]['name'] : '';
    3051         $bp_title_parts = array( $bp->bp_options_title, $subnav );
    3052 
    3053     // A single item from a component other than groups.
     3075    // A single item from a component other than Members.
    30543076    } elseif ( bp_is_single_item() ) {
    3055         $bp_title_parts = array( $bp->bp_options_title, $bp->bp_options_nav[ bp_current_item() ][ bp_current_action() ]['name'] );
     3077        $component_id = bp_current_component();
     3078
     3079        if ( ! empty( $bp->{$component_id}->nav ) ) {
     3080            $secondary_nav_item = $bp->{$component_id}->nav->get_secondary( array(
     3081                'parent_slug' => bp_current_item(),
     3082                'slug'        => bp_current_action()
     3083            ), false );
     3084
     3085            if ( $secondary_nav_item ) {
     3086                $secondary_nav_item = reset( $secondary_nav_item );
     3087            }
     3088        }
     3089
     3090        $single_item_subnav = '';
     3091
     3092        if ( ! empty( $secondary_nav_item->name ) ) {
     3093            $single_item_subnav = $secondary_nav_item->name;
     3094        }
     3095
     3096        $bp_title_parts = array( $bp->bp_options_title, $single_item_subnav );
    30563097
    30573098    // An index or directory.
     
    34293470 *
    34303471 * @since 1.7.0
    3431  *
     3472 * @since 2.6.0 Introduced the `$component` parameter.
     3473 *
     3474 * @param string $component Optional. Component whose nav items are being fetched.
    34323475 * @return array A multidimensional array of all navigation items.
    34333476 */
    3434 function bp_get_nav_menu_items() {
    3435     $menus = $selected_menus = array();
    3436 
    3437     // Get the second level menus.
    3438     foreach ( (array) buddypress()->bp_options_nav as $parent_menu => $sub_menus ) {
    3439 
    3440         // The root menu's ID is "xprofile", but the Profile submenus are using "profile". See BP_Core::setup_nav().
    3441         if ( 'profile' === $parent_menu ) {
    3442             $parent_menu = 'xprofile';
    3443         }
    3444 
    3445         // Sort the items in this menu's navigation by their position property.
    3446         $second_level_menus = (array) $sub_menus;
    3447         usort( $second_level_menus, '_bp_nav_menu_sort' );
    3448 
    3449         // Iterate through the second level menus.
    3450         foreach( $second_level_menus as $sub_nav ) {
    3451 
    3452             // Skip items we don't have access to.
    3453             if ( empty( $sub_nav['user_has_access'] ) ) {
    3454                 continue;
    3455             }
    3456 
    3457             // Add this menu.
    3458             $menu         = new stdClass;
    3459             $menu->class  = array( 'menu-child' );
    3460             $menu->css_id = $sub_nav['css_id'];
    3461             $menu->link   = $sub_nav['link'];
    3462             $menu->name   = $sub_nav['name'];
    3463             $menu->parent = $parent_menu;  // Associate this sub nav with a top-level menu.
    3464 
    3465             // If we're viewing this item's screen, record that we need to mark its parent menu to be selected.
    3466             if ( $sub_nav['slug'] == bp_current_action() ) {
    3467                 $menu->class[]    = 'current-menu-item';
    3468                 $selected_menus[] = $parent_menu;
    3469             }
    3470 
    3471             $menus[] = $menu;
    3472         }
    3473     }
    3474 
    3475     // Get the top-level menu parts (Friends, Groups, etc) and sort by their position property.
    3476     $top_level_menus = (array) buddypress()->bp_nav;
    3477     usort( $top_level_menus, '_bp_nav_menu_sort' );
    3478 
    3479     // Iterate through the top-level menus.
    3480     foreach ( $top_level_menus as $nav ) {
    3481 
    3482         // Skip items marked as user-specific if you're not on your own profile.
    3483         if ( empty( $nav['show_for_displayed_user'] ) && ! bp_core_can_edit_settings()  ) {
    3484             continue;
    3485         }
    3486 
     3477function bp_get_nav_menu_items( $component = 'members' ) {
     3478    $bp    = buddypress();
     3479    $menus = array();
     3480
     3481    if ( ! isset( $bp->{$component}->nav ) ) {
     3482        return $menus;
     3483    }
     3484
     3485    // Get the item nav and build the menus.
     3486    foreach ( $bp->{$component}->nav->get_item_nav() as $nav_menu ) {
    34873487        // Get the correct menu link. See https://buddypress.trac.wordpress.org/ticket/4624.
    3488         $link = bp_loggedin_user_domain() ? str_replace( bp_loggedin_user_domain(), bp_displayed_user_domain(), $nav['link'] ) : trailingslashit( bp_displayed_user_domain() . $nav['link'] );
     3488        $link = bp_loggedin_user_domain() ? str_replace( bp_loggedin_user_domain(), bp_displayed_user_domain(), $nav_menu->link ) : trailingslashit( bp_displayed_user_domain() . $nav_menu->link );
    34893489
    34903490        // Add this menu.
    34913491        $menu         = new stdClass;
    34923492        $menu->class  = array( 'menu-parent' );
    3493         $menu->css_id = $nav['css_id'];
    3494         $menu->link   = $link;
    3495         $menu->name   = $nav['name'];
     3493        $menu->css_id = $nav_menu->css_id;
     3494        $menu->link   = $nav_menu->link;
     3495        $menu->name   = $nav_menu->name;
    34963496        $menu->parent = 0;
    34973497
    3498         // Check if we need to mark this menu as selected.
    3499         if ( in_array( $nav['css_id'], $selected_menus ) ) {
    3500             $menu->class[] = 'current-menu-parent';
     3498        if ( ! empty( $nav_menu->children ) ) {
     3499            $submenus = array();
     3500
     3501            foreach( $nav_menu->children as $sub_menu ) {
     3502                $submenu = new stdClass;
     3503                $submenu->class  = array( 'menu-child' );
     3504                $submenu->css_id = $sub_menu->css_id;
     3505                $submenu->link   = $sub_menu->link;
     3506                $submenu->name   = $sub_menu->name;
     3507                $submenu->parent = $nav_menu->slug;
     3508
     3509                // If we're viewing this item's screen, record that we need to mark its parent menu to be selected.
     3510                if ( $sub_menu->slug == bp_current_action() ) {
     3511                    $menu->class[]    = 'current-menu-parent';
     3512                    $submenu->class[] = 'current-menu-item';
     3513                }
     3514
     3515                $submenus[] = $submenu;
     3516            }
    35013517        }
    35023518
    35033519        $menus[] = $menu;
     3520
     3521        if ( ! empty( $submenus ) ) {
     3522            $menus = array_merge( $menus, $submenus );
     3523        }
    35043524    }
    35053525
Note: See TracChangeset for help on using the changeset viewer.