Skip to:
Content

BuddyPress.org


Ignore:
Timestamp:
05/12/2016 05:19:06 PM (6 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-buddybar.php

    r10497 r10745  
    1414
    1515/**
    16  * Add an item to the main BuddyPress navigation array.
     16 * Add an item to the primary navigation of the specified component.
    1717 *
    1818 * @since 1.1.0
     19 * @since 2.6.0 Introduced the `$component` parameter.
     20 *
     21 * @param array|string $args {
     22 *     Array describing the new nav item.
     23 *     @type string      $name                    Display name for the nav item.
     24 *     @type string      $slug                    Unique URL slug for the nav item.
     25 *     @type bool|string $item_css_id             Optional. 'id' attribute for the nav item. Default: the value of `$slug`.
     26 *     @type bool        $show_for_displayed_user Optional. Whether the nav item should be visible when viewing a
     27 *                                                member profile other than your own. Default: true.
     28 *     @type bool        $site_admin_only         Optional. Whether the nav item should be visible only to site admins
     29 *                                                (those with the 'bp_moderate' cap). Default: false.
     30 *     @type int         $position                Optional. Numerical index specifying where the item should appear in
     31 *                                                the nav array. Default: 99.
     32 *     @type callable    $screen_function         The callback function that will run when the nav item is clicked.
     33 *     @type bool|string $default_subnav_slug     Optional. The slug of the default subnav item to select when the nav
     34 *                                                item is clicked.
     35 * }
     36 * @param string $component The component the navigation is attached to. Defaults to 'members'.
     37 * @return bool|null Returns false on failure.
     38 */
     39function bp_core_new_nav_item( $args, $component = 'members' ) {
     40    if ( ! bp_is_active( $component ) ) {
     41        return;
     42    }
     43
     44    $defaults = array(
     45        'name'                    => false, // Display name for the nav item.
     46        'slug'                    => false, // URL slug for the nav item.
     47        'item_css_id'             => false, // The CSS ID to apply to the HTML of the nav item.
     48        'show_for_displayed_user' => true,  // When viewing another user does this nav item show up?
     49        'site_admin_only'         => false, // Can only site admins see this nav item?
     50        'position'                => 99,    // Index of where this nav item should be positioned.
     51        'screen_function'         => false, // The name of the function to run when clicked.
     52        'default_subnav_slug'     => false  // The slug of the default subnav item to select when clicked.
     53    );
     54
     55    $r = wp_parse_args( $args, $defaults );
     56
     57    // Validate nav link data.
     58    $nav_item = bp_core_create_nav_link( $r, $component );
     59
     60    /*
     61     * To mimic legacy behavior, if bp_core_create_nav_link() returns false, we make
     62     * an early exit and don't attempt to register the screen function.
     63     */
     64    if ( false === $nav_item ) {
     65        return false;
     66    }
     67
     68    // Then, hook the screen function for the added nav item.
     69    $hooked = bp_core_register_nav_screen_function( $nav_item );
     70    if ( false === $hooked ){
     71        return false;
     72    }
     73
     74    /**
     75     * Fires after adding an item to the main BuddyPress navigation array.
     76     * Note that, when possible, the more specific action hooks
     77     * `bp_core_create_nav_link` or `bp_core_register_nav_screen_function`
     78     * should be used.
     79     *
     80     * @since 1.5.0
     81     *
     82     * @param array $r        Parsed arguments for the nav item.
     83     * @param array $args     Originally passed in arguments for the nav item.
     84     * @param array $defaults Default arguments for a nav item.
     85     */
     86    do_action( 'bp_core_new_nav_item', $r, $args, $defaults );
     87}
     88
     89/**
     90 * Add a link to the main BuddyPress navigation.
     91 *
     92 * @since 2.4.0
     93 * @since 2.6.0 Introduced the `$component` parameter. Began returning a BP_Nav_Item object on success.
     94 *
     95 * @param array|string $args {
     96 *     Array describing the new nav item.
     97 *     @type string      $name                    Display name for the nav item.
     98 *     @type string      $slug                    Unique URL slug for the nav item.
     99 *     @type bool|string $item_css_id             Optional. 'id' attribute for the nav item. Default: the value of `$slug`.
     100 *     @type bool        $show_for_displayed_user Optional. Whether the nav item should be visible when viewing a
     101 *                                                member profile other than your own. Default: true.
     102 *     @type bool        $site_admin_only         Optional. Whether the nav item should be visible only to site admins
     103 *                                                (those with the 'bp_moderate' cap). Default: false.
     104 *     @type int         $position                Optional. Numerical index specifying where the item should appear in
     105 *                                                the nav array. Default: 99.
     106 *     @type callable    $screen_function         The callback function that will run when the nav item is clicked.
     107 *     @type bool|string $default_subnav_slug     Optional. The slug of the default subnav item to select when the nav
     108 *                                                item is clicked.
     109 * }
     110 * @param string $component Optional. Component that the nav belongs to.
     111 * @return bool|BP_Nav_Item Returns false on failure, new nav item on success.
     112 */
     113function bp_core_create_nav_link( $args = '', $component = 'members' ) {
     114    $bp = buddypress();
     115
     116    $defaults = array(
     117        'name'                    => false, // Display name for the nav item.
     118        'slug'                    => false, // URL slug for the nav item.
     119        'item_css_id'             => false, // The CSS ID to apply to the HTML of the nav item.
     120        'show_for_displayed_user' => true,  // When viewing another user does this nav item show up?
     121        'site_admin_only'         => false, // Can only site admins see this nav item?
     122        'position'                => 99,    // Index of where this nav item should be positioned.
     123        'screen_function'         => false, // The name of the function to run when clicked.
     124        'default_subnav_slug'     => false  // The slug of the default subnav item to select when clicked.
     125    );
     126
     127    $r = wp_parse_args( $args, $defaults );
     128
     129    // If we don't have the required info we need, don't create this nav item.
     130    if ( empty( $r['name'] ) || empty( $r['slug'] ) ) {
     131        return false;
     132    }
     133
     134    // If this is for site admins only and the user is not one, don't create the nav item.
     135    if ( ! empty( $r['site_admin_only'] ) && ! bp_current_user_can( 'bp_moderate' ) ) {
     136        return false;
     137    }
     138
     139    if ( empty( $r['item_css_id'] ) ) {
     140        $r['item_css_id'] = $r['slug'];
     141    }
     142
     143    $nav_item = array(
     144        'name'                    => $r['name'],
     145        'slug'                    => $r['slug'],
     146        'link'                    => trailingslashit( bp_loggedin_user_domain() . $r['slug'] ),
     147        'css_id'                  => $r['item_css_id'],
     148        'show_for_displayed_user' => $r['show_for_displayed_user'],
     149        'position'                => $r['position'],
     150        'screen_function'         => &$r['screen_function'],
     151        'default_subnav_slug'     => $r['default_subnav_slug']
     152    );
     153
     154    // Add the item to the nav.
     155    $retval = buddypress()->{$component}->nav->add_nav( $nav_item );
     156
     157    /**
     158     * Fires after a link is added to the main BuddyPress nav.
     159     *
     160     * @since 2.4.0
     161     * @since 2.6.0 Added `$component` parameter.
     162     *
     163     * @param array  $r         Parsed arguments for the nav item.
     164     * @param array  $args      Originally passed in arguments for the nav item.
     165     * @param array  $defaults  Default arguments for a nav item.
     166     * @param string $component Component that the nav belongs to.
     167     */
     168    do_action( 'bp_core_create_nav_link', $r, $args, $defaults, $component );
     169
     170    return $retval;
     171}
     172
     173/**
     174 * Register a screen function for an item in the main nav array.
     175 *
     176 * @since 2.4.0
    19177 *
    20178 * @param array|string $args {
     
    35193 * @return bool|null Returns false on failure.
    36194 */
    37 function bp_core_new_nav_item( $args = '' ) {
    38 
    39     $defaults = array(
    40         'name'                    => false, // Display name for the nav item.
    41         'slug'                    => false, // URL slug for the nav item.
    42         'item_css_id'             => false, // The CSS ID to apply to the HTML of the nav item.
    43         'show_for_displayed_user' => true,  // When viewing another user does this nav item show up?
    44         'site_admin_only'         => false, // Can only site admins see this nav item?
    45         'position'                => 99,    // Index of where this nav item should be positioned.
    46         'screen_function'         => false, // The name of the function to run when clicked.
    47         'default_subnav_slug'     => false  // The slug of the default subnav item to select when clicked.
    48     );
    49 
    50     $r = wp_parse_args( $args, $defaults );
    51 
    52     // First, add the nav item link to the bp_nav array.
    53     $created = bp_core_create_nav_link( $r );
    54 
    55     // To mimic the existing behavior, if bp_core_create_nav_link()
    56     // returns false, we make an early exit and don't attempt to register
    57     // the screen function.
    58     if ( false === $created ) {
    59         return false;
    60     }
    61 
    62     // Then, hook the screen function for the added nav item.
    63     $hooked = bp_core_register_nav_screen_function( $r );
    64     if ( false === $hooked ){
    65         return false;
    66     }
    67 
    68     /**
    69      * Fires after adding an item to the main BuddyPress navigation array.
    70      * Note that, when possible, the more specific action hooks
    71      * `bp_core_create_nav_link` or `bp_core_register_nav_screen_function`
    72      * should be used.
    73      *
    74      * @since 1.5.0
    75      *
    76      * @param array $r        Parsed arguments for the nav item.
    77      * @param array $args     Originally passed in arguments for the nav item.
    78      * @param array $defaults Default arguments for a nav item.
    79      */
    80     do_action( 'bp_core_new_nav_item', $r, $args, $defaults );
    81 }
    82 
    83 /**
    84  * Add a link to the main BuddyPress navigation array.
    85  *
    86  * @since 2.4.0
    87  *
    88  * @param array|string $args {
    89  *     Array describing the new nav item.
    90  *     @type string      $name                    Display name for the nav item.
    91  *     @type string      $slug                    Unique URL slug for the nav item.
    92  *     @type bool|string $item_css_id             Optional. 'id' attribute for the nav item. Default: the value of `$slug`.
    93  *     @type bool        $show_for_displayed_user Optional. Whether the nav item should be visible when viewing a
    94  *                                                member profile other than your own. Default: true.
    95  *     @type bool        $site_admin_only         Optional. Whether the nav item should be visible only to site admins
    96  *                                                (those with the 'bp_moderate' cap). Default: false.
    97  *     @type int         $position                Optional. Numerical index specifying where the item should appear in
    98  *                                                the nav array. Default: 99.
    99  *     @type callable    $screen_function         The callback function that will run when the nav item is clicked.
    100  *     @type bool|string $default_subnav_slug     Optional. The slug of the default subnav item to select when the nav
    101  *                                                item is clicked.
    102  * }
    103  * @return bool|null Returns false on failure.
    104  */
    105 function bp_core_create_nav_link( $args = '' ) {
    106     $bp = buddypress();
    107 
    108     $defaults = array(
    109         'name'                    => false, // Display name for the nav item.
    110         'slug'                    => false, // URL slug for the nav item.
    111         'item_css_id'             => false, // The CSS ID to apply to the HTML of the nav item.
    112         'show_for_displayed_user' => true,  // When viewing another user does this nav item show up?
    113         'site_admin_only'         => false, // Can only site admins see this nav item?
    114         'position'                => 99,    // Index of where this nav item should be positioned.
    115         'screen_function'         => false, // The name of the function to run when clicked.
    116         'default_subnav_slug'     => false  // The slug of the default subnav item to select when clicked.
    117     );
    118 
    119     $r = wp_parse_args( $args, $defaults );
    120 
    121     // If we don't have the required info we need, don't create this nav item.
    122     if ( empty( $r['name'] ) || empty( $r['slug'] ) ) {
    123         return false;
    124     }
    125 
    126     // If this is for site admins only and the user is not one, don't create the nav item.
    127     if ( ! empty( $r['site_admin_only'] ) && ! bp_current_user_can( 'bp_moderate' ) ) {
    128         return false;
    129     }
    130 
    131     if ( empty( $r['item_css_id'] ) ) {
    132         $r['item_css_id'] = $r['slug'];
    133     }
    134 
    135     $bp->bp_nav[$r['slug']] = array(
    136         'name'                    => $r['name'],
    137         'slug'                    => $r['slug'],
    138         'link'                    => trailingslashit( bp_loggedin_user_domain() . $r['slug'] ),
    139         'css_id'                  => $r['item_css_id'],
    140         'show_for_displayed_user' => $r['show_for_displayed_user'],
    141         'position'                => $r['position'],
    142         'screen_function'         => &$r['screen_function'],
    143         'default_subnav_slug'     => $r['default_subnav_slug']
    144     );
    145 
    146     /**
    147      * Fires after a link is added to the main BuddyPress navigation array.
    148      *
    149      * @since 2.4.0
    150      *
    151      * @param array $r        Parsed arguments for the nav item.
    152      * @param array $args     Originally passed in arguments for the nav item.
    153      * @param array $defaults Default arguments for a nav item.
    154      */
    155     do_action( 'bp_core_create_nav_link', $r, $args, $defaults );
    156 }
    157 
    158 /**
    159  * Register a screen function for an item in the main nav array.
    160  *
    161  * @since 2.4.0
    162  *
    163  * @param array|string $args {
    164  *     Array describing the new nav item.
    165  *     @type string      $name                    Display name for the nav item.
    166  *     @type string      $slug                    Unique URL slug for the nav item.
    167  *     @type bool|string $item_css_id             Optional. 'id' attribute for the nav item. Default: the value of `$slug`.
    168  *     @type bool        $show_for_displayed_user Optional. Whether the nav item should be visible when viewing a
    169  *                                                member profile other than your own. Default: true.
    170  *     @type bool        $site_admin_only         Optional. Whether the nav item should be visible only to site admins
    171  *                                                (those with the 'bp_moderate' cap). Default: false.
    172  *     @type int         $position                Optional. Numerical index specifying where the item should appear in
    173  *                                                the nav array. Default: 99.
    174  *     @type callable    $screen_function         The callback function that will run when the nav item is clicked.
    175  *     @type bool|string $default_subnav_slug     Optional. The slug of the default subnav item to select when the nav
    176  *                                                item is clicked.
    177  * }
    178  * @return bool|null Returns false on failure.
    179  */
    180195function bp_core_register_nav_screen_function( $args = '' ) {
    181196    $bp = buddypress();
     
    291306    $r = wp_parse_args( $args, $defaults );
    292307
    293     if ( $function = $bp->bp_nav[$r['parent_slug']]['screen_function'] ) {
     308    // This is specific to Members - it's not available in Groups.
     309    $parent_nav = $bp->members->nav->get_primary( array( 'slug' => $r['parent_slug'] ), false );
     310
     311    if ( ! $parent_nav ) {
     312        return ;
     313    }
     314
     315    $parent_nav = reset( $parent_nav );
     316
     317    if ( ! empty( $parent_nav->screen_function ) ) {
    294318        // Remove our screen hook if screen function is callable.
    295         if ( is_callable( $function ) ) {
    296             remove_action( 'bp_screens', $function, 3 );
    297         }
    298     }
    299 
    300     $bp->bp_nav[$r['parent_slug']]['screen_function'] = &$r['screen_function'];
    301 
    302     if ( bp_is_current_component( $r['parent_slug'] ) ) {
     319        if ( is_callable( $parent_nav->screen_function ) ) {
     320            remove_action( 'bp_screens', $parent_nav->screen_function, 3 );
     321        }
     322    }
     323
     324    // Edit the screen function for the parent nav
     325    $bp->members->nav->edit_nav( array(
     326        'screen_function'     => &$r['screen_function'],
     327        'default_subnav_slug' => $r['subnav_slug'],
     328    ), $parent_nav->slug );
     329
     330    if ( bp_is_current_component( $parent_nav->slug ) ) {
    303331
    304332        // The only way to tell whether to set the subnav is to peek at the unfiltered_uri
    305333        // Find the component.
    306         $component_uri_key = array_search( $r['parent_slug'], $bp->unfiltered_uri );
     334        $component_uri_key = array_search( $parent_nav->slug, $bp->unfiltered_uri );
    307335
    308336        if ( false !== $component_uri_key ) {
     
    340368
    341369/**
    342  * Sort the navigation menu items.
    343  *
    344  * The sorting is split into a separate function because it can only happen
    345  * after all plugins have had a chance to register their navigation items.
    346  *
    347  * @since 1.0.0
    348  *
    349  * @return bool|null Returns false on failure.
    350  */
    351 function bp_core_sort_nav_items() {
    352     $bp = buddypress();
    353 
    354     if ( empty( $bp->bp_nav ) || ! is_array( $bp->bp_nav ) ) {
    355         return false;
    356     }
    357 
    358     $temp = array();
    359 
    360     foreach ( (array) $bp->bp_nav as $slug => $nav_item ) {
    361         if ( empty( $temp[$nav_item['position']] ) ) {
    362             $temp[$nav_item['position']] = $nav_item;
    363         } else {
    364             // Increase numbers here to fit new items in.
    365             do {
    366                 $nav_item['position']++;
    367             } while ( ! empty( $temp[$nav_item['position']] ) );
    368 
    369             $temp[$nav_item['position']] = $nav_item;
    370         }
    371     }
    372 
    373     ksort( $temp );
    374     $bp->bp_nav = &$temp;
    375 }
    376 add_action( 'wp_head',    'bp_core_sort_nav_items' );
    377 add_action( 'admin_head', 'bp_core_sort_nav_items' );
    378 
    379 /**
    380  * Add a subnav item to the BuddyPress navigation.
     370 * Add an item to secondary navigation of the specified component.
    381371 *
    382372 * @since 1.1.0
     373 * @since 2.6.0 Introduced the `$component` parameter.
    383374 *
    384375 * @param array|string $args {
     
    404395 *                                          Admin Bar menu for group admins. Default: false.
    405396 * }
     397 * @param string $component The component the navigation is attached to. Defaults to 'members'.
    406398 * @return bool|null Returns false on failure.
    407399 */
    408 function bp_core_new_subnav_item( $args = '' ) {
    409 
    410     // First, add the subnav item link to the bp_options_nav array.
    411     $created = bp_core_create_subnav_link( $args );
    412 
    413     // To mimic the existing behavior, if bp_core_create_subnav_link()
    414     // returns false, we make an early exit and don't attempt to register
    415     // the screen function.
    416     if ( false === $created ) {
     400function bp_core_new_subnav_item( $args, $component = null ) {
     401    // Backward compatibility for plugins using `bp_core_new_subnav_item()` without `$component`
     402    // to add group subnav items.
     403    if ( null === $component && bp_is_active( 'groups' ) && bp_is_group() && isset( $args['parent_slug'] ) ) {
     404        /*
     405         * Assume that this item is intended to belong to the current group if:
     406         * a) the 'parent_slug' is the same as the slug of the current group, or
     407         * b) the 'parent_slug' starts with the slug of the current group, and the members nav doesn't have
     408         *    a primary item with that slug
     409         */
     410        $group_slug = bp_get_current_group_slug();
     411        if (
     412            $group_slug === $args['parent_slug'] ||
     413            ( 0 === strpos( $args['parent_slug'], $group_slug ) && ! buddypress()->members->nav->get_primary( array( 'slug' => $args['parent_slug'] ), false ) )
     414        ) {
     415            $component = 'groups';
     416        }
     417    }
     418
     419    if ( ! $component ) {
     420        $component = 'members';
     421    }
     422
     423    if ( ! bp_is_active( $component ) ) {
     424        return;
     425    }
     426
     427    // First, register the subnav item in the nav.
     428    $subnav_item = bp_core_create_subnav_link( $args, $component );
     429
     430    /*
     431     * To mimic legacy behavior, if bp_core_create_subnav_link() returns false, we make an
     432     * early exit and don't attempt to register the screen function.
     433     */
     434    if ( false === $subnav_item ) {
    417435        return false;
    418436    }
    419437
    420438    // Then, hook the screen function for the added subnav item.
    421     $hooked = bp_core_register_subnav_screen_function( $args );
     439    $hooked = bp_core_register_subnav_screen_function( $subnav_item, $component );
    422440    if ( false === $hooked ) {
    423441        return false;
     
    429447 *
    430448 * @since 2.4.0
     449 * @since 2.6.0 Introduced the `$component` parameter. Began returning a BP_Nav_Item object on success.
    431450 *
    432451 * @param array|string $args {
     
    456475 *                                          Default: false.
    457476 * }
    458  * @return bool|null Returns false on failure.
    459  */
    460 function bp_core_create_subnav_link( $args = '' ) {
     477 * @param string $component The component the navigation is attached to. Defaults to 'members'.
     478 * @return bool|BP_Nav_Item Returns false on failure, new nav item on success.
     479 */
     480function bp_core_create_subnav_link( $args = '', $component = 'members' ) {
    461481    $bp = buddypress();
    462482
     
    484504        $r['link'] = trailingslashit( $r['parent_url'] . $r['slug'] );
    485505
     506        $parent_nav = $bp->{$component}->nav->get_primary( array( 'slug' => $r['parent_slug'] ), false );
     507
    486508        // If this sub item is the default for its parent, skip the slug.
    487         if ( ! empty( $bp->bp_nav[$r['parent_slug']]['default_subnav_slug'] ) && $r['slug'] == $bp->bp_nav[$r['parent_slug']]['default_subnav_slug'] ) {
    488             $r['link'] = trailingslashit( $r['parent_url'] );
     509        if ( $parent_nav ) {
     510            $parent_nav_item = reset( $parent_nav );
     511            if ( ! empty( $parent_nav_item->default_subnav_slug ) && $r['slug'] === $parent_nav_item->default_subnav_slug ) {
     512                $r['link'] = trailingslashit( $r['parent_url'] );
     513            }
    489514        }
    490515    }
     
    503528        'link'              => $r['link'],
    504529        'slug'              => $r['slug'],
     530        'parent_slug'       => $r['parent_slug'],
    505531        'css_id'            => $r['item_css_id'],
    506532        'position'          => $r['position'],
     
    511537    );
    512538
    513     $bp->bp_options_nav[$r['parent_slug']][$r['slug']] = $subnav_item;
     539    return buddypress()->{$component}->nav->add_nav( $subnav_item );
    514540}
    515541
     
    518544 *
    519545 * @since 2.4.0
     546 * @since 2.6.0 Introduced the `$component` parameter.
    520547 *
    521548 * @param array|string $args {
     
    542569 *                                       Default: false.
    543570 * }
     571 * @param string $component The component the navigation is attached to. Defaults to 'members'.
    544572 * @return bool|null Returns false on failure.
    545573 */
    546 function bp_core_register_subnav_screen_function( $args = '' ) {
     574function bp_core_register_subnav_screen_function( $args = '', $component = 'members' ) {
    547575    $bp = buddypress();
    548576
     
    556584    ) );
    557585
    558     /**
     586    /*
    559587     * Hook the screen function for the added subnav item. But this only needs to
    560588     * be done if this subnav item is the current view, and the user has access to the
     
    569597     *       and this subnav item is the default for the parent item (which we check by
    570598     *       comparing this subnav item's screen function with the screen function of the
    571      *       parent nav item in $bp->bp_nav). This condition only arises when viewing a
    572      *       user, since groups should always have an action set.
     599     *       parent nav item in the component's primary nav). This condition only arises
     600     *       when viewing a user, since groups should always have an action set.
    573601     */
    574602
     
    578606    }
    579607
     608    $parent_nav = $bp->{$component}->nav->get_primary( array( 'slug' => $r['parent_slug'] ), false );
     609
    580610    // If we *do* meet condition (2), then the added subnav item is currently being requested.
    581     if ( ( bp_current_action() && bp_is_current_action( $r['slug'] ) ) || ( bp_is_user() && ! bp_current_action() && ( $r['screen_function'] == $bp->bp_nav[$r['parent_slug']]['screen_function'] ) ) ) {
     611    if ( ( bp_current_action() && bp_is_current_action( $r['slug'] ) ) || ( bp_is_user() && ! bp_current_action() && ! empty( $parent_nav->screen_function ) && $r['screen_function'] == $parent_nav->screen_function ) ) {
    582612
    583613        // If this is for site admins only and the user is not one, don't create the subnav item.
     
    586616        }
    587617
    588         $hooked = bp_core_maybe_hook_new_subnav_screen_function( $r );
     618        $hooked = bp_core_maybe_hook_new_subnav_screen_function( $r, $component );
    589619
    590620        // If redirect args have been returned, perform the redirect now.
     
    599629 *
    600630 * @since 2.1.0
    601  *
    602  * @param array $subnav_item The subnav array added to bp_options_nav in `bp_core_new_subnav_item()`.
     631 * @since 2.6.0 Introduced the `$component` parameter.
     632 *
     633 * @param array  $subnav_item The subnav array added to the secondary navigation of
     634 *                            the component in bp_core_new_subnav_item().
     635 * @param string $component   The component the navigation is attached to. Defaults to 'members'.
    603636 * @return array
    604637 */
    605 function bp_core_maybe_hook_new_subnav_screen_function( $subnav_item ) {
     638function bp_core_maybe_hook_new_subnav_screen_function( $subnav_item, $component = 'members' ) {
    606639    $retval = array(
    607640        'status' => '',
     
    646679            } elseif ( bp_is_user() ) {
    647680
     681                $parent_nav_default = $bp->{$component}->nav->get_primary( array( 'slug' => $bp->default_component ), false );
     682                if ( $parent_nav_default ) {
     683                    $parent_nav_default_item = reset( $parent_nav_default );
     684                }
     685
    648686                // Redirect to the displayed user's default
    649687                // component, as long as that component is
    650688                // publicly accessible.
    651                 if ( bp_is_my_profile() || ! empty( $bp->bp_nav[ $bp->default_component ]['show_for_displayed_user'] ) ) {
     689                if ( bp_is_my_profile() || ( isset( $parent_nav_default_item ) && $parent_nav_default_item->show_for_displayed_user ) ) {
    652690                    $message     = __( 'You do not have access to this page.', 'buddypress' );
    653691                    $redirect_to = bp_displayed_user_domain();
     
    692730
    693731/**
    694  * Sort all subnavigation arrays.
    695  *
    696  * @since 1.1.0
    697  *
    698  * @return bool|null Returns false on failure.
    699  */
    700 function bp_core_sort_subnav_items() {
     732 * Check whether a given nav item has subnav items.
     733 *
     734 * @since 1.5.0
     735 * @since 2.6.0 Introduced the `$component` parameter.
     736 *
     737 * @param string $nav_item  The slug of the top-level nav item whose subnav items you're checking.
     738 *                          Default: the current component slug.
     739 * @param string $component The component the navigation is attached to. Defaults to 'members'.
     740 * @return bool $has_subnav True if the nav item is found and has subnav items; false otherwise.
     741 */
     742function bp_nav_item_has_subnav( $nav_item = '', $component = 'members' ) {
    701743    $bp = buddypress();
    702744
    703     if ( empty( $bp->bp_options_nav ) || !is_array( $bp->bp_options_nav ) )
    704         return false;
    705 
    706     foreach ( (array) $bp->bp_options_nav as $parent_slug => $subnav_items ) {
    707         if ( !is_array( $subnav_items ) )
    708             continue;
    709 
    710         foreach ( (array) $subnav_items as $subnav_item ) {
    711             if ( empty( $temp[$subnav_item['position']]) )
    712                 $temp[$subnav_item['position']] = $subnav_item;
    713             else {
    714                 // Increase numbers here to fit new items in.
    715                 do {
    716                     $subnav_item['position']++;
    717                 } while ( !empty( $temp[$subnav_item['position']] ) );
    718 
    719                 $temp[$subnav_item['position']] = $subnav_item;
    720             }
    721         }
    722         ksort( $temp );
    723         $bp->bp_options_nav[$parent_slug] = &$temp;
    724         unset( $temp );
    725     }
    726 }
    727 add_action( 'wp_head',    'bp_core_sort_subnav_items' );
    728 add_action( 'admin_head', 'bp_core_sort_subnav_items' );
    729 
    730 /**
    731  * Check whether a given nav item has subnav items.
    732  *
    733  * @since 1.5.0
    734  *
    735  * @param string $nav_item The slug of the top-level nav item whose subnav items you're checking.
    736  *                         Default: the current component slug.
    737  * @return bool $has_subnav True if the nav item is found and has subnav items; false otherwise.
    738  */
    739 function bp_nav_item_has_subnav( $nav_item = '' ) {
    740     $bp = buddypress();
    741 
    742     if ( !$nav_item )
     745    if ( ! isset( $bp->{$component}->nav ) ) {
     746        return false;
     747    }
     748
     749    if ( ! $nav_item ) {
    743750        $nav_item = bp_current_component();
    744751
    745     $has_subnav = isset( $bp->bp_options_nav[$nav_item] ) && count( $bp->bp_options_nav[$nav_item] ) > 0;
     752        if ( bp_is_group() ) {
     753            $nav_item = bp_current_item();
     754        }
     755    }
     756
     757    $has_subnav = (bool) $bp->{$component}->nav->get_secondary( array( 'parent_slug' => $nav_item ), false );
    746758
    747759    /**
     
    757769
    758770/**
    759  * Remove a nav item from the navigation array.
     771 * Deletes an item from the primary navigation of the specified component.
    760772 *
    761773 * @since 1.0.0
    762  *
    763  * @param int $parent_id The slug of the parent navigation item.
    764  * @return bool Returns false on failure, ie if the nav item can't be found.
    765  */
    766 function bp_core_remove_nav_item( $parent_id ) {
     774 * @since 2.6.0 Introduced the `$component` parameter.
     775 *
     776 * @param string $slug      The slug of the primary navigation item.
     777 * @param string $component The component the navigation is attached to. Defaults to 'members'.
     778 * @return bool Returns false on failure, True on success.
     779 */
     780function bp_core_remove_nav_item( $slug, $component = null ) {
    767781    $bp = buddypress();
    768782
    769     // Unset subnav items for this nav item.
    770     if ( isset( $bp->bp_options_nav[$parent_id] ) && is_array( $bp->bp_options_nav[$parent_id] ) ) {
    771         foreach( (array) $bp->bp_options_nav[$parent_id] as $subnav_item ) {
    772             bp_core_remove_subnav_item( $parent_id, $subnav_item['slug'] );
    773         }
    774     }
    775 
    776     if ( empty( $bp->bp_nav[ $parent_id ] ) )
    777         return false;
    778 
    779     if ( $function = $bp->bp_nav[$parent_id]['screen_function'] ) {
    780         // Remove our screen hook if screen function is callable.
    781         if ( is_callable( $function ) ) {
    782             remove_action( 'bp_screens', $function, 3 );
    783         }
    784     }
    785 
    786     unset( $bp->bp_nav[$parent_id] );
    787 }
    788 
    789 /**
    790  * Remove a subnav item from the navigation array.
    791  *
    792  * @since 1.0.0
    793  *
    794  * @param string $parent_id The slug of the parent navigation item.
    795  * @param string $slug      The slug of the subnav item to be removed.
    796  */
    797 function bp_core_remove_subnav_item( $parent_id, $slug ) {
    798     $bp = buddypress();
    799 
    800     $screen_function = isset( $bp->bp_options_nav[$parent_id][$slug]['screen_function'] )
    801         ? $bp->bp_options_nav[$parent_id][$slug]['screen_function']
    802         : false;
    803 
    804     if ( ! empty( $screen_function ) ) {
     783    // Backward compatibility for removing group nav items using the group slug as `$parent_slug`.
     784    if ( ! $component && bp_is_active( 'groups' ) && isset( $bp->groups->nav ) ) {
     785        if ( $bp->groups->nav->get_primary( array( 'slug' => $slug ) ) ) {
     786            return bp_core_remove_nav_item( $slug, 'groups' );
     787        }
     788    }
     789
     790    if ( ! $component ) {
     791        $component = 'members';
     792    }
     793
     794    if ( ! isset( $bp->{$component}->nav ) ) {
     795        return false;
     796    }
     797
     798    $screen_functions = $bp->{$component}->nav->delete_nav( $slug );
     799
     800    // Reset backcompat nav items so that subsequent references will be correct.
     801    if ( buddypress()->do_nav_backcompat ) {
     802        $bp->bp_nav->reset();
     803        $bp->bp_options_nav->reset();
     804    }
     805
     806    if ( ! is_array( $screen_functions ) ) {
     807        return false;
     808    }
     809
     810    foreach ( $screen_functions as $screen_function ) {
    805811        // Remove our screen hook if screen function is callable.
    806812        if ( is_callable( $screen_function ) ) {
     
    809815    }
    810816
    811     unset( $bp->bp_options_nav[$parent_id][$slug] );
    812 
    813     if ( isset( $bp->bp_options_nav[$parent_id] ) && !count( $bp->bp_options_nav[$parent_id] ) )
    814         unset($bp->bp_options_nav[$parent_id]);
     817    return true;
     818}
     819
     820/**
     821 * Deletes an item from the secondary navigation of the specified component.
     822 *
     823 * @since 1.0.0
     824 * @since 2.6.0 Introduced the `$component` parameter.
     825 *
     826 * @param string $parent_slug The slug of the primary navigation item.
     827 * @param string $slug        The slug of the secondary item to be removed.
     828 * @param string $component   The component the navigation is attached to. Defaults to 'members'.
     829 * @return bool Returns false on failure, True on success.
     830 */
     831function bp_core_remove_subnav_item( $parent_slug, $slug, $component = null ) {
     832    $bp = buddypress();
     833
     834    // Backward compatibility for removing group nav items using the group slug as `$parent_slug`.
     835    if ( ! $component && bp_is_active( 'groups' ) && isset( $bp->groups->nav ) ) {
     836        if ( $bp->groups->nav->get_primary( array( 'slug' => $parent_slug ) ) ) {
     837            return bp_core_remove_subnav_item( $slug, $parent_slug, 'groups' );
     838        }
     839    }
     840
     841    if ( ! $component ) {
     842        $component = 'members';
     843    }
     844
     845    if ( ! isset( $bp->{$component}->nav ) ) {
     846        return false;
     847    }
     848
     849    $screen_functions = $bp->{$component}->nav->delete_nav( $slug, $parent_slug );
     850
     851    // Reset backcompat nav items so that subsequent references will be correct.
     852    if ( buddypress()->do_nav_backcompat ) {
     853        $bp->bp_nav->reset();
     854        $bp->bp_options_nav->reset();
     855    }
     856
     857    if ( ! is_array( $screen_functions ) ) {
     858        return false;
     859    }
     860
     861    $screen_function = reset( $screen_functions );
     862
     863    // Remove our screen hook if screen function is callable.
     864    if ( is_callable( $screen_function ) ) {
     865        remove_action( 'bp_screens', $screen_function, 3 );
     866    }
     867
     868    return true;
    815869}
    816870
     
    819873 *
    820874 * @since 1.0.0
     875 * @since 2.6.0 Introduced the `$component` parameter.
    821876 *
    822877 * @param string $parent_slug The slug of the parent navigation item.
    823  */
    824 function bp_core_reset_subnav_items( $parent_slug ) {
     878 * @param string $component   The component the navigation is attached to. Defaults to 'members'.
     879 */
     880function bp_core_reset_subnav_items( $parent_slug, $component = 'members' ) {
    825881    $bp = buddypress();
    826882
    827     unset( $bp->bp_options_nav[$parent_slug] );
     883    if ( ! isset( $bp->{$component}->nav ) ) {
     884        return;
     885    }
     886
     887    $subnav_items = $bp->{$component}->nav->get_secondary( array( 'parent_slug' => $parent_slug ), false );
     888
     889    if ( ! $subnav_items ) {
     890        return;
     891    }
     892
     893    foreach( $subnav_items as $subnav_item ) {
     894        $bp->{$component}->nav->delete_nav( $subnav_item->slug, $parent_slug );
     895    }
    828896}
    829897
Note: See TracChangeset for help on using the changeset viewer.