Skip to:
Content

BuddyPress.org

Ticket #3961: 3961.1.patch

File 3961.1.patch, 74.8 KB (added by dcavins, 3 years ago)

First patch

  • src/bp-core/admin/bp-core-admin-schema.php

    diff --git src/bp-core/admin/bp-core-admin-schema.php src/bp-core/admin/bp-core-admin-schema.php
    index 6f9043a..44506bf 100644
    function bp_core_install_groups() { 
    202202                                slug varchar(200) NOT NULL,
    203203                                description longtext NOT NULL,
    204204                                status varchar(10) NOT NULL DEFAULT 'public',
     205                                parent_id bigint(20) NOT NULL DEFAULT 0,
    205206                                enable_forum tinyint(1) NOT NULL DEFAULT '1',
    206207                                date_created datetime NOT NULL,
    207208                                KEY creator_id (creator_id),
    208                                 KEY status (status)
     209                                KEY status (status),
     210                                KEY parent_id (parent_id)
    209211                        ) {$charset_collate};";
    210212
    211213        $sql[] = "CREATE TABLE {$bp_prefix}bp_groups_members (
  • src/bp-core/admin/bp-core-admin-settings.php

    diff --git src/bp-core/admin/bp-core-admin-settings.php src/bp-core/admin/bp-core-admin-settings.php
    index c13029f..dfa2e0a 100644
    function bp_admin_setting_callback_group_creation() { 
    192192}
    193193
    194194/**
     195 * Allow hierarchical groups.
     196 *
     197 * @since 2.7.0
     198 *
     199 */
     200function bp_admin_setting_callback_allow_hierarchical_groups() {
     201?>
     202
     203        <input id="bp_allow_hierarchical_groups" name="bp_allow_hierarchical_groups" type="checkbox" value="1" <?php checked(bp_allow_hierarchical_groups() ); ?> />
     204        <label for="bp_allow_hierarchical_groups"><?php _e( 'Allow hierarchical groups', 'buddypress' ); ?></label>
     205        <p class="description"><?php _e( 'Allow groups to be associated with a parent group.', 'buddypress' ); ?></p>
     206
     207<?php
     208}
     209
     210/**
    195211 * 'Enable group avatars' field markup.
    196212 *
    197213 * @since 2.3.0
  • src/bp-core/bp-core-caps.php

    diff --git src/bp-core/bp-core-caps.php src/bp-core/bp-core-caps.php
    index 2963f9f..6789838 100644
    function bp_current_user_can( $capability, $args = array() ) { 
    277277                $blog_id = bp_get_root_blog_id();
    278278        }
    279279
    280         $args   = array( $blog_id, $capability, $args );
    281         $retval = call_user_func_array( 'current_user_can_for_blog', $args );
     280        $retval = call_user_func_array( 'current_user_can_for_blog', array( $blog_id, $capability, $args ) );
    282281
    283282        /**
    284283         * Filters whether or not the current user has a given capability.
    function bp_user_can( $user_id, $capability, $args = array() ) { 
    319318        }
    320319
    321320        $switched = is_multisite() ? switch_to_blog( $site_id ) : false;
    322         $args     = array( $user_id, $capability, $args );
    323         $retval   = call_user_func_array( 'user_can', $args );
     321        $retval   = call_user_func_array( 'user_can', array( $user_id, $capability, $args ) );
    324322
    325323        /**
    326324         * Filters whether or not the specified user has a given capability on a given site.
  • src/bp-core/bp-core-options.php

    diff --git src/bp-core/bp-core-options.php src/bp-core/bp-core-options.php
    index dac2409..d2a2ec7 100644
    function bp_restrict_group_creation( $default = true ) { 
    719719}
    720720
    721721/**
     722 * Are hierarchical groups allowed?
     723 *
     724 * @since 2.7.0
     725 *
     726 * @param bool $default Optional. Fallback value if not found in the database.
     727 *                      Default: false.
     728 * @return bool True if hierarchical groups are allowed, otherwise false.
     729 */
     730function bp_allow_hierarchical_groups( $default = false ) {
     731
     732        /**
     733         * Filters whether or not hierarchical groups are allowed.
     734         *
     735         * @since 2.7.0
     736         *
     737         * @param bool $value Whether or not hierarchical groups are allowed.
     738         */
     739        return (bool) apply_filters( 'bp_allow_hierarchical_groups', (bool) bp_get_option( 'bp_allow_hierarchical_groups', $default ) );
     740}
     741
     742/**
    722743 * Should the old BuddyBar be forced in place of the WP admin bar?
    723744 *
    724745 * @since 1.6.0
  • src/bp-core/bp-core-template.php

    diff --git src/bp-core/bp-core-template.php src/bp-core/bp-core-template.php
    index 22bffd1..2563f1a 100644
    function bp_is_group_members() { 
    27862786}
    27872787
    27882788/**
     2789 * Is the current page a group's subgroup directory?
     2790 *
     2791 * Eg http://example.com/groups/mygroup/hierarchy/.
     2792 *
     2793 * @since 2.7.0
     2794 *
     2795 * @return bool True if the current page is a group's directory of subgroups.
     2796 */
     2797function bp_is_group_subgroups() {
     2798        return (bool) ( bp_is_groups_component() && bp_is_current_action( 'hierarchy' ) );
     2799}
     2800
     2801/**
    27892802 * Is the current page a group's Invites page?
    27902803 *
    27912804 * Eg http://example.com/groups/mygroup/send-invites/.
  • src/bp-core/bp-core-update.php

    diff --git src/bp-core/bp-core-update.php src/bp-core/bp-core-update.php
    index 16608a4..c16a324 100644
    function bp_update_to_2_5() { 
    508508 * 2.7.0 update routine.
    509509 *
    510510 * - Add email unsubscribe salt.
     511 * - Add `parent_id` column to groups table.
    511512 *
    512513 * @since 2.7.0
    513514 */
    514515function bp_update_to_2_7() {
    515516        bp_add_option( 'bp-emails-unsubscribe-salt', base64_encode( wp_generate_password( 64, true, true ) ) );
     517
     518        /*
     519         * Also handled by `bp_core_install()`.
     520         * Add `parent_id` column to groups table.
     521         */
     522        if ( bp_is_active( 'groups' ) ) {
     523                bp_core_install_groups();
     524        }
    516525}
    517526
    518527/**
  • src/bp-core/classes/class-bp-admin.php

    diff --git src/bp-core/classes/class-bp-admin.php src/bp-core/classes/class-bp-admin.php
    index 2cb10b0..a7131c6 100644
    class BP_Admin { 
    429429                        add_settings_field( 'bp_restrict_group_creation', __( 'Group Creation', 'buddypress' ), 'bp_admin_setting_callback_group_creation',   'buddypress', 'bp_groups' );
    430430                        register_setting( 'buddypress', 'bp_restrict_group_creation', 'intval' );
    431431
     432                        // Allow hierarchical groups.
     433                        add_settings_field( 'bp_allow_hierarchical_groups', __( 'Hierarchical Groups', 'buddypress' ), 'bp_admin_setting_callback_allow_hierarchical_groups',   'buddypress', 'bp_groups' );
     434                        register_setting( 'buddypress', 'bp_allow_hierarchical_groups', 'intval' );
     435
    432436                        // Allow group avatars.
    433437                        add_settings_field( 'bp-disable-group-avatar-uploads', __( 'Group Photo Uploads', 'buddypress' ), 'bp_admin_setting_callback_group_avatar_uploads', 'buddypress', 'bp_groups' );
    434438                        register_setting( 'buddypress', 'bp-disable-group-avatar-uploads', 'intval' );
  • src/bp-groups/bp-groups-actions.php

    diff --git src/bp-groups/bp-groups-actions.php src/bp-groups/bp-groups-actions.php
    index 4a3c510..40b6d0d 100644
    function groups_action_create_group() { 
    205205                        elseif ( 'hidden' == $_POST['group-status'] )
    206206                                $group_status = 'hidden';
    207207
    208                         if ( !$bp->groups->new_group_id = groups_create_group( array( 'group_id' => $bp->groups->new_group_id, 'status' => $group_status, 'enable_forum' => $group_enable_forum ) ) ) {
     208                        // Hierarchical groups: Setting the parent ID.
     209                        $parent_id = 0;
     210                        if ( bp_allow_hierarchical_groups() && isset( $_POST['parent-id'] ) ) {
     211                                $parent_id = intval( $_POST['parent-id'] );
     212                        }
     213
     214                        if ( !$bp->groups->new_group_id = groups_create_group( array( 'group_id' => $bp->groups->new_group_id, 'status' => $group_status, 'enable_forum' => $group_enable_forum, 'parent_id' => $parent_id ) ) ) {
    209215                                bp_core_add_message( __( 'There was an error saving group details. Please try again.', 'buddypress' ), 'error' );
    210216                                bp_core_redirect( trailingslashit( bp_get_groups_directory_permalink() . 'create/step/' . bp_get_groups_current_create_step() ) );
    211217                        }
    function groups_action_group_feed() { 
    566572        ) );
    567573}
    568574add_action( 'bp_actions', 'groups_action_group_feed' );
     575
     576/**
     577 * Update orphaned child groups when the parent is deleted.
     578 *
     579 * @since 2.7.0
     580 *
     581 * @param BP_Groups_Group $group Instance of the group item being deleted.
     582 */
     583function bp_update_orphaned_groups_on_group_delete( $group ) {
     584        // Get child groups and set the parent to the deleted parent's parent.
     585        $grandparent_group_id = ! empty( $group->parent_id ) ? $group->parent_id : 0;
     586        $children = bp_groups_get_child_groups( $group->id );
     587
     588        foreach ( $children as $cgroup ) {
     589                $child_group = groups_get_group( array( 'group_id' => $cgroup->id ) );
     590                $child_group->parent_id = $grandparent_group_id;
     591                $child_group->save();
     592        }
     593}
     594add_action( 'bp_groups_delete_group', 'bp_update_orphaned_groups_on_group_delete', 10, 2 );
     595
     596/**
     597 * Save a group's allowed_subgroup_creators setting as group metadata.
     598 *
     599 * @since 2.7.0
     600 *
     601 * @param int    $group_id   ID of the group to update.
     602 */
     603function bp_groups_settings_save_allowed_subgroups_creators( $group_id ) {
     604        if ( bp_allow_hierarchical_groups() &&
     605                 isset( $_POST['allowed-subgroup-creators'] ) &&
     606                 in_array( $_POST['allowed-subgroup-creators'], array( 'noone', 'admin', 'mod', 'member' ) ) ) {
     607                groups_update_groupmeta( $group_id, 'allowed_subgroup_creators', $_POST['allowed-subgroup-creators'] );
     608        }
     609}
     610add_action( 'groups_group_settings_edited', 'bp_groups_settings_save_allowed_subgroups_creators' );
     611add_action( 'bp_group_admin_edit_after', 'bp_groups_settings_save_allowed_subgroups_creators' );
     612
     613/**
     614 * Save a group's allowed_subgroup_creators setting from the create group screen.
     615 *
     616 * @since 2.7.0
     617 */
     618function bp_groups_create_step_save_allowed_subgroups_creators() {
     619        $group_id = buddypress()->groups->new_group_id;
     620        bp_groups_settings_save_allowed_subgroups_creators( $group_id );
     621}
     622add_action( 'groups_create_group_step_save_group-settings', 'bp_groups_create_step_save_allowed_subgroups_creators' );
  • src/bp-groups/bp-groups-admin.php

    diff --git src/bp-groups/bp-groups-admin.php src/bp-groups/bp-groups-admin.php
    index 5a2a0bf..e38dac8 100644
    function bp_groups_admin_load() { 
    278278                $allowed_invite_status = apply_filters( 'groups_allowed_invite_status', array( 'members', 'mods', 'admins' ) );
    279279                $invite_status         = in_array( $_POST['group-invite-status'], (array) $allowed_invite_status ) ? $_POST['group-invite-status'] : 'members';
    280280
    281                 if ( !groups_edit_group_settings( $group_id, $enable_forum, $status, $invite_status ) ) {
     281                // Set a parent group ID.
     282                $parent_id = ( isset( $_POST['parent-id'] ) ) ? (int) $_POST['parent-id'] : 0;
     283
     284                if ( !groups_edit_group_settings( $group_id, $enable_forum, $status, $invite_status, $parent_id ) ) {
    282285                        $error = $group_id;
    283286                }
    284287
    function bp_groups_admin_edit_metabox_settings( $item ) { 
    826829                </fieldset>
    827830        </div>
    828831
     832        <?php if ( bp_allow_hierarchical_groups() ) : ?>
     833                <div class="bp-groups-settings-section" id="bp-groups-settings-section-group-hierarchy">
     834                        <fieldset>
     835                                <legend><?php _e( 'Parent Group', 'buddypress' ); ?></legend>
     836
     837                                <?php
     838                                $current_parent_group_id = bp_groups_get_parent_group_id( $item->id );
     839                                $possible_parent_groups = bp_groups_get_possible_parent_groups( $item->id, bp_loggedin_user_id() );
     840                                if ( $possible_parent_groups ) :
     841                                        ?>
     842                                        <select name="parent-id">
     843                                                <option value="0" <?php selected( 0, $current_parent_group_id ); ?>><?php echo _x( 'None selected', 'The option that sets a group to be a top-level group and have no parent.', 'buddypress' ); ?></option>
     844                                        <?php foreach ( $possible_parent_groups as $possible_parent_group ) {
     845                                                ?>
     846                                                <option value="<?php echo $possible_parent_group->id; ?>" <?php selected( $current_parent_group_id, $possible_parent_group->id ); ?>><?php echo $possible_parent_group->name; ?></option>
     847                                                <?php
     848                                        }
     849                                        ?>
     850                                        </select>
     851                                        <?php
     852                                else :
     853                                        ?>
     854                                        <p><?php echo __( 'There are no groups available to be a parent to this group.', 'buddypress' ); ?></p>
     855                                        <?php
     856                                endif;
     857                                ?>
     858                        </fieldset>
     859
     860                        <fieldset>
     861
     862                                <legend><?php _e( 'Which members of this group are allowed to create subgroups?', 'buddypress' ); ?></legend>
     863
     864                                <?php
     865                                $subgroup_creators = groups_get_groupmeta( $item->id, 'allowed_subgroup_creators' );
     866                                if ( ! $subgroup_creators ) {
     867                                        $subgroup_creators = 'noone';
     868                                }
     869                                ?>
     870                                <ul>
     871                                        <li><label for="allowed-subgroup-creators-members"><input type="radio" name="allowed-subgroup-creators" id="allowed-subgroup-creators-members" value="member" <?php checked( $subgroup_creators, 'member' ); ?> /> <?php _e( 'All group members', 'buddypress' ); ?></label></li>
     872
     873                                        <li><label for="allowed-subgroup-creators-mods"><input type="radio" name="allowed-subgroup-creators" id="allowed-subgroup-creators-mods" value="mod" <?php checked( $subgroup_creators, 'mod' ); ?> /> <?php _e( 'Group admins and mods only', 'buddypress' ); ?></label></li>
     874
     875                                        <li><label for="allowed-subgroup-creators-admins"><input type="radio" name="allowed-subgroup-creators" id="allowed-subgroup-creators-admins" value="admin" <?php checked( $subgroup_creators, 'admin' ); ?> /> <?php _e( 'Group admins only', 'buddypress' ); ?></label></li>
     876
     877                                        <li><label for="allowed-subgroup-creators-noone"><input type="radio" name="allowed-subgroup-creators" id="allowed-subgroup-creators-noone" value="noone" <?php checked( $subgroup_creators, 'noone' ); ?> /> <?php _e( 'No one', 'buddypress' ); ?></label></li>
     878                                </ul>
     879                        </fieldset>
     880                </div>
     881        <?php endif; ?>
    829882<?php
    830883}
    831884
  • src/bp-groups/bp-groups-cache.php

    diff --git src/bp-groups/bp-groups-cache.php src/bp-groups/bp-groups-cache.php
    index 1781b4f..4d6f6ac 100644
    add_action( 'groups_create_group_step_complete', 'groups_clear_group_object_cach 
    7070 */
    7171function bp_groups_delete_group_cache( $group_id = 0 ) {
    7272        wp_cache_delete( $group_id, 'bp_groups' );
     73        wp_cache_delete( 'last_changed', 'bp_groups' );
    7374}
    7475add_action( 'groups_delete_group',     'bp_groups_delete_group_cache' );
    7576add_action( 'groups_update_group',     'bp_groups_delete_group_cache' );
  • src/bp-groups/bp-groups-filters.php

    diff --git src/bp-groups/bp-groups-filters.php src/bp-groups/bp-groups-filters.php
    index 4aefa10..eae6f11 100644
    function bp_groups_default_avatar( $avatar, $params ) { 
    346346
    347347        return $avatar;
    348348}
     349
     350
     351/**
     352 * Determine whether the current user can create a subgroup of a particular group.
     353 *
     354 * @since 2.7.0
     355 *
     356 * @param bool   $retval     Whether or not the current user has the capability.
     357 * @param string $capability The capability being checked for.
     358 * @param int    $site_id    Site ID. Defaults to the BP root blog.
     359 * @param array  $args       Array of extra arguments passed.
     360 *
     361 * @return bool
     362 */
     363function bp_groups_current_user_can_create_subgroups( $retval, $capability, $site_id, $args ) {
     364        if ( 'create_subgroups' != $capability ) {
     365                return $retval;
     366        }
     367
     368        $user_id = bp_loggedin_user_id();
     369        return bp_groups_user_can_create_subgroups( $retval, $user_id, $capability, $site_id, $args );
     370}
     371add_filter( 'bp_current_user_can', 'bp_groups_current_user_can_create_subgroups', 10, 4 );
     372
     373/**
     374 * Determine whether a specific user can create a subgroup of a particular group.
     375 *
     376 * @since 2.7.0
     377 *
     378 * @param bool   $retval     Whether or not the current user has the capability.
     379 * @param int    $user_id    ID of user to check.
     380 * @param string $capability The capability being checked for.
     381 * @param int    $site_id    Site ID. Defaults to the BP root blog.
     382 * @param array  $args       Array of extra arguments passed.
     383 *
     384 * @return bool
     385 */
     386function bp_groups_user_can_create_subgroups( $retval, $user_id, $capability, $site_id, $args ) {
     387        if ( 'create_subgroups' != $capability ) {
     388                return $retval;
     389        }
     390
     391        if ( ! bp_allow_hierarchical_groups() ) {
     392                return false;
     393        }
     394
     395        // We need to know which group is in question.
     396        if ( empty( $args['group_id'] ) ) {
     397                return false;
     398        }
     399        $group_id = (int) $args['group_id'];
     400
     401        // Possible settings for the group meta setting 'allowed_subgroup_creators'
     402        $creator_setting = groups_get_groupmeta( $group_id, 'allowed_subgroup_creators' );
     403        switch ( $creator_setting ) {
     404                case 'admin' :
     405                        $retval = groups_is_user_admin( $user_id, $group_id );
     406                        break;
     407
     408                case 'mod' :
     409                        $retval = ( groups_is_user_mod( $user_id, $group_id ) ||
     410                                                groups_is_user_admin( $user_id, $group_id ) );
     411                        break;
     412
     413                case 'member' :
     414                        $retval = groups_is_user_member( $user_id, $group_id );
     415                        break;
     416
     417                case 'noone' :
     418                default :
     419                        // @TODO: This seems weird, but I can imagine situations where only site admins should be able to associate groups.
     420                        $retval = bp_user_can( $user_id, 'bp_moderate' );
     421                        break;
     422        }
     423
     424        return $retval;
     425}
     426add_filter( 'bp_user_can', 'bp_groups_user_can_create_subgroups', 10, 5 );
  • src/bp-groups/bp-groups-functions.php

    diff --git src/bp-groups/bp-groups-functions.php src/bp-groups/bp-groups-functions.php
    index cb0753e..193f24f 100644
    function groups_get_group( $args = '' ) { 
    8787 *     @type string   $slug         The group slug.
    8888 *     @type string   $status       The group's status. Accepts 'public', 'private' or
    8989 *                                  'hidden'. Defaults to 'public'.
     90 *     @type int      $parent_id    The ID of the parent group. Default: 0.
    9091 *     @type int      $enable_forum Optional. Whether the group has a forum enabled.
    9192 *                                  If the legacy forums are enabled for this group
    9293 *                                  or if a bbPress forum is enabled for the group,
    function groups_create_group( $args = '' ) { 
    105106                'description'  => '',
    106107                'slug'         => '',
    107108                'status'       => 'public',
     109                'parent_id'    => 0,
    108110                'enable_forum' => 0,
    109111                'date_created' => bp_core_current_time()
    110112        );
    function groups_create_group( $args = '' ) { 
    151153        $group->description  = $description;
    152154        $group->slug         = $slug;
    153155        $group->status       = $status;
     156        $group->parent_id    = $parent_id;
    154157        $group->enable_forum = (int) $enable_forum;
    155158        $group->date_created = $date_created;
    156159
    function groups_edit_base_group_details( $group_id, $group_name, $group_desc, $n 
    270273 *                                   to the group. 'members', 'mods', or 'admins'.
    271274 * @return bool True on success, false on failure.
    272275 */
    273 function groups_edit_group_settings( $group_id, $enable_forum, $status, $invite_status = false ) {
     276function groups_edit_group_settings( $group_id, $enable_forum, $status, $invite_status = false, $parent_id = false ) {
    274277
    275278        $group = groups_get_group( array( 'group_id' => $group_id ) );
    276279        $group->enable_forum = $enable_forum;
    function groups_edit_group_settings( $group_id, $enable_forum, $status, $invite_ 
    285288        // Now update the status.
    286289        $group->status = $status;
    287290
     291        // Update the parent ID if necessary.
     292        if ( false !== $parent_id ) {
     293                $group->parent_id = $parent_id;
     294        }
     295
    288296        if ( !$group->save() )
    289297                return false;
    290298
    function groups_get_groups( $args = '' ) { 
    703711                'user_id'            => false,          // Pass a user_id to limit to only groups that this user is a member of.
    704712                'include'            => false,          // Only include these specific groups (group_ids).
    705713                'exclude'            => false,          // Do not include these specific groups (group_ids).
     714                'parent_id'          => false,          // Get groups that are children of the specified group(s).
    706715                'search_terms'       => false,          // Limit to groups that match these search terms.
    707716                'group_type'         => '',
    708717                'group_type__in'     => '',
    function groups_get_groups( $args = '' ) { 
    722731                'user_id'            => $r['user_id'],
    723732                'include'            => $r['include'],
    724733                'exclude'            => $r['exclude'],
     734                'parent_id'          => $r['parent_id'],
    725735                'search_terms'       => $r['search_terms'],
    726736                'group_type'         => $r['group_type'],
    727737                'group_type__in'     => $r['group_type__in'],
    function bp_remove_group_type_on_group_delete( $group_id = 0 ) { 
    23932403        bp_groups_set_group_type( $group_id, '' );
    23942404}
    23952405add_action( 'groups_delete_group', 'bp_remove_group_type_on_group_delete' );
     2406
     2407// Hierarchical groups
     2408
     2409/**
     2410 * Get the child groups for a specific group.
     2411 *
     2412 * To return all child groups, leave the $user_id parameter empty. To return
     2413 * only those child groups visible to a specific user, specify a $user_id.
     2414 *
     2415 * @since 2.7.0
     2416 *
     2417 * @param  int   $group_id ID of the group.
     2418 * @param  int   $user_id  ID of a user to check group visibility for.
     2419 *
     2420 * @return array Array of group objects.
     2421 */
     2422function bp_groups_get_child_groups( $group_id = false, $user_id = false ) {
     2423        /*
     2424         * Passing a group id of 0 would find all top-level groups, which could be
     2425         * intentional. We only try to find the current group when the $group_id is false.
     2426         */
     2427        if ( $group_id === false ) {
     2428                $group_id = bp_get_current_group_id();
     2429                if ( ! $group_id ) {
     2430                        // If we can't resolve the group_id, don't proceed with a zero value.
     2431                        return array();
     2432                }
     2433        }
     2434
     2435        // @TODO: Do we need to check whether the specified user can see the parent group in question?
     2436
     2437        // Check the cache first.
     2438        $last_changed = wp_cache_get( 'last_changed', 'bp_groups' );
     2439        if ( false === $last_changed ) {
     2440                $last_changed = microtime();
     2441                wp_cache_set( 'last_changed', $last_changed, 'bp_groups' );
     2442        }
     2443
     2444        $cache_key = 'bp_groups_child_groups_of_' . $group_id . '_' . $last_changed;
     2445        $children  = wp_cache_get( $cache_key, 'bp_groups' );
     2446
     2447        if ( false === $children ) {
     2448                // Fetch all child groups.
     2449                $child_args = array(
     2450                        'parent_id' => $group_id,
     2451                        'show_hidden' => true,
     2452                );
     2453                $children = groups_get_groups( $child_args );
     2454                $children = $children['groups'];
     2455
     2456                // Set the cache to avoid duplicate requests.
     2457                wp_cache_set( $cache_key, $children, 'bp_groups' );
     2458        }
     2459
     2460        // If a user ID has been specified, we filter hidden groups accordingly.
     2461        if ( false !== $user_id && ! bp_user_can( $user_id, 'bp_moderate' ) ) {
     2462                foreach ( $children as $k => $group ) {
     2463                        // Check whether the user should be able to see this group.
     2464                        // @TODO: Use group capabilities for this when possible.
     2465                        if ( 'hidden' == $group->status && ! groups_is_user_member( $user_id, $group->id ) ) {
     2466                                unset( $children[$k] );
     2467                        }
     2468                }
     2469        }
     2470
     2471        return $children;
     2472}
     2473
     2474/**
     2475 * Get the child group IDs for a specific group.
     2476 *
     2477 * To return all child groups, leave the $user_id parameter empty. To return
     2478 * only those child groups visible to a specific user, specify a $user_id.
     2479 *
     2480 * @since 2.7.0
     2481 *
     2482 * @param  int   $group_id ID of the group.
     2483 * @param  int   $user_id  ID of a user to check group visibility for.
     2484 *
     2485 * @return array Array of group IDs.
     2486 */
     2487function bp_groups_get_child_group_ids( $group_id = false, $user_id = false ) {
     2488        /*
     2489         * Passing a group id of 0 would find all top-level groups, which could be
     2490         * intentional. We only try to find the current group when the $group_id is false.
     2491         */
     2492        if ( $group_id === false ) {
     2493                $group_id = bp_get_current_group_id();
     2494                if ( ! $group_id ) {
     2495                        // If we can't resolve the group_id, don't proceed with a zero value.
     2496                        return array();
     2497                }
     2498        }
     2499
     2500        $child_groups    = bp_groups_get_child_groups( $group_id, $user_id );
     2501        $child_group_ids = wp_list_pluck( $child_groups, 'id' );
     2502
     2503        // Convert IDs to integers.
     2504        return array_map( 'intval', $child_group_ids );
     2505}
     2506
     2507/**
     2508 * Does a specific group have child groups?
     2509 *
     2510 * To check for the actual existence of child groups, leave the $user_id
     2511 * parameter empty. To check whether any exist that are visible to a user,
     2512 * supply a $user_id.
     2513 *
     2514 * @since 2.7.0
     2515 *
     2516 * @param  int   $group_id ID of the group.
     2517 * @param  int   $user_id  ID of a user to check group visibility for.
     2518 *
     2519 * @return bool True if true, false if not.
     2520 */
     2521function bp_groups_has_children( $group_id = false, $user_id = false ) {
     2522        /*
     2523         * Passing a group id of 0 finds all top-level groups, which could be
     2524         * intentional. Try to find the current group only when the $group_id is false.
     2525         */
     2526        if ( $group_id === false ) {
     2527                $group_id = bp_get_current_group_id();
     2528                if ( ! $group_id ) {
     2529                        // If we can't resolve the group_id, don't proceed with a zero value.
     2530                        return false;
     2531                }
     2532        }
     2533
     2534        $children = bp_groups_get_child_groups( $group_id, $user_id );
     2535        return ! empty ( $children ) ? true : false;
     2536}
     2537
     2538/**
     2539 * Get all groups that are descendants of a specific group.
     2540 *
     2541 * To return all descendent groups, leave the $user_id parameter empty. To return
     2542 * only those child groups visible to a specific user, specify a $user_id.
     2543 *
     2544 * @since 2.7.0
     2545 *
     2546 * @param  int   $group_id ID of the group.
     2547 * @param  int   $user_id  ID of a user to check group visibility for.
     2548 *
     2549 * @return array Array of group objects.
     2550 */
     2551function bp_groups_get_descendent_groups( $group_id = false, $user_id = false ) {
     2552        /*
     2553         * Passing a group id of 0 would find all top-level groups, which could be
     2554         * intentional. We only try to find the current group when the $group_id is false.
     2555         */
     2556        if ( $group_id === false ) {
     2557                $group_id = bp_get_current_group_id();
     2558                if ( ! $group_id ) {
     2559                        // If we can't resolve the group_id, don't proceed with a zero value.
     2560                        return array();
     2561                }
     2562        }
     2563
     2564        // Check the cache first.
     2565        $last_changed = wp_cache_get( 'last_changed', 'bp_groups' );
     2566        if ( false === $last_changed ) {
     2567                $last_changed = microtime();
     2568                wp_cache_set( 'last_changed', $last_changed, 'bp_groups' );
     2569        }
     2570
     2571        $cache_key   = 'bp_groups_descendants_of_' . $group_id . '_' . $last_changed;
     2572        $descendants = wp_cache_get( $cache_key, 'bp_groups' );
     2573
     2574        if ( false === $descendants ) {
     2575                // Start from the group specified.
     2576                $parents = array( $group_id );
     2577                $descendants = array();
     2578
     2579                // We work down the tree until no new children are found.
     2580                while ( $parents ) {
     2581                        // Fetch all child groups.
     2582                        $child_args = array(
     2583                                'parent_id' => $parents,
     2584                                'show_hidden' => true,
     2585                        );
     2586                        $children = groups_get_groups( $child_args );
     2587
     2588                        // Add groups to the set of found groups.
     2589                        $descendants = array_merge( $descendants, $children['groups'] );
     2590
     2591                        // Set up the next set of parents.
     2592                        $parents = wp_list_pluck( $children['groups'], 'id' );
     2593                }
     2594
     2595                // Set the cache to avoid duplicate requests.
     2596                wp_cache_set( $cache_key, $descendants, 'bp_groups' );
     2597        }
     2598
     2599        // If a user ID has been specified, we filter hidden groups accordingly.
     2600        if ( false !== $user_id && ! bp_user_can( $user_id, 'bp_moderate' ) ) {
     2601                foreach ( $descendants as $k => $group ) {
     2602                        // Check whether the user should be able to see this group.
     2603                        // @TODO: Use group capabilities for this when possible.
     2604                        if ( 'hidden' == $group->status && ! groups_is_user_member( $user_id, $group->id ) ) {
     2605                                unset( $descendants[$k] );
     2606                        }
     2607                }
     2608        }
     2609
     2610        return $descendants;
     2611}
     2612
     2613/**
     2614 * Get the parent group ID for a specific group.
     2615 *
     2616 * To return the parent group regardless of visibility, leave the $user_id
     2617 * parameter empty. To return the parent only when visible to a specific user,
     2618 * specify a $user_id.
     2619 *
     2620 * @since 2.7.0
     2621 *
     2622 * @param  int   $group_id ID of the group.
     2623 * @param  int   $user_id  ID of a user to check group visibility for.
     2624 *
     2625 * @return int ID of parent group.
     2626 */
     2627function bp_groups_get_parent_group_id( $group_id = false, $user_id = false ) {
     2628        /*
     2629         * Passing a group id of 0 would find all top-level groups, which could be
     2630         * intentional. We only try to find the current group when the $group_id is false.
     2631         */
     2632        if ( $group_id === false ) {
     2633                $group_id = bp_get_current_group_id();
     2634                if ( ! $group_id ) {
     2635                        // If we can't resolve the group_id, don't proceed with a zero value.
     2636                        return array();
     2637                }
     2638        }
     2639
     2640        $group     = groups_get_group( array( 'group_id' => $group_id ) );
     2641        $parent_id = $group->parent_id;
     2642
     2643        // If the user is specified, is the parent group visible to that user?
     2644        // @TODO: This could make use of group visibility when available.
     2645        if ( false !== $user_id && ! bp_user_can( $user_id, 'bp_moderate' ) ) {
     2646                $parent_group = groups_get_group( array( 'group_id' => $parent_id) );
     2647                if ( 'hidden' == $parent_group->status && ! groups_is_user_member( $user_id, $parent_group->id ) ) {
     2648                        // If the group is not visible to the user, break the chain.
     2649                        $parent_id = 0;
     2650                }
     2651        }
     2652
     2653        return (int) $parent_id;
     2654}
     2655
     2656/**
     2657 * Get an array of group ids that are ancestors of a specific group.
     2658 *
     2659 * To return all ancestor groups, leave the $user_id parameter empty. To return
     2660 * only those ancestor groups visible to a specific user, specify a $user_id.
     2661 * Note that if groups the user can't see are encountered, the chain of ancestry
     2662 * is stopped. Also note that the order here is useful: the first element is the
     2663 * parent group id, the second is the grandparent group id and so on.
     2664 *
     2665 * @since 2.7.0
     2666 *
     2667 * @param  int   $group_id ID of the group.
     2668 * @param  int   $user_id  ID of a user to check group visibility for.
     2669 *
     2670 * @return array Array of group objects.
     2671 */
     2672function bp_groups_get_ancestor_group_ids( $group_id = false, $user_id = false ) {
     2673        /*
     2674         * Passing a group id of 0 would find all top-level groups, which could be
     2675         * intentional. We only try to find the current group when the $group_id is false.
     2676         */
     2677        if ( $group_id === false ) {
     2678                $group_id = bp_get_current_group_id();
     2679                if ( ! $group_id ) {
     2680                        // If we can't resolve the group_id, don't proceed with a zero value.
     2681                        return array();
     2682                }
     2683        }
     2684
     2685        $ancestors = array();
     2686
     2687        // We work up the tree until no new parent is found.
     2688        while ( $group_id ) {
     2689                $parent_group_id = bp_groups_get_parent_group_id( $group_id, $user_id );
     2690                if ( $parent_group_id ) {
     2691                        $ancestors[] = $parent_group_id;
     2692                }
     2693                // Set a new group id to work from.
     2694                $group_id = $parent_group_id;
     2695        }
     2696
     2697        return $ancestors;
     2698}
     2699
     2700/**
     2701 * Get an array of possible parent group ids for a specific group and user.
     2702 *
     2703 * To be a candidate for group parenthood, the group cannot be a descendent of
     2704 * this group, and the user must be allowed to create child groups in that group.
     2705 *
     2706 * @since 2.7.0
     2707 *
     2708 * @param  int   $group_id ID of the group.
     2709 * @param  int   $user_id  ID of a user to check group visibility for.
     2710 *
     2711 * @return array Array of group objects.
     2712 */
     2713function bp_groups_get_possible_parent_groups( $group_id = false, $user_id = false ) {
     2714        /*
     2715         * Passing a group id of 0 would find all top-level groups, which could be
     2716         * intentional. We only try to find the current group when the $group_id is false.
     2717         */
     2718        if ( false === $group_id ) {
     2719                $group_id = bp_get_current_group_id();
     2720                if ( ! $group_id ) {
     2721                        // If we can't resolve the group_id, don't proceed with a zero value.
     2722                        return array();
     2723                }
     2724        }
     2725
     2726        if ( false === $user_id ) {
     2727                $user_id = bp_loggedin_user_id();
     2728                if ( ! $user_id ) {
     2729                        // If we can't resolve the user_id, don't proceed with a zero value.
     2730                        return array();
     2731                }
     2732        }
     2733
     2734        // First, get a list of descendants (don't pass a user id--we want them all).
     2735        $descendants = bp_groups_get_descendent_groups( $group_id );
     2736        $exclude_ids = wp_list_pluck( $descendants, 'id' );
     2737        // Also exclude the current group.
     2738        $exclude_ids[] = $group_id;
     2739
     2740        // Make sure to exclude descendent groups.
     2741        $args = array(
     2742                'orderby'            => 'name',
     2743                'order'              => 'ASC',
     2744                'populate_extras'    => false,
     2745                'exclude'            => $exclude_ids,
     2746                'show_hidden'        => true,
     2747        );
     2748        // If the user is not a site admin, limit the set to groups she belongs to.
     2749        if ( ! bp_user_can( $user_id, 'bp_moderate' ) ) {
     2750                $args['user_id'] = $user_id;
     2751        }
     2752        $possible_parents = groups_get_groups( $args );
     2753        foreach ( $possible_parents['groups'] as $k => $group ) {
     2754                // Check whether the user can create child groups of this group.
     2755                if ( ! bp_user_can( $user_id, 'create_subgroups', array( 'group_id' => $group->id ) ) ) {
     2756                        unset( $possible_parents['groups'][$k] );
     2757                }
     2758        }
     2759
     2760        return $possible_parents['groups'];
     2761}
  • src/bp-groups/bp-groups-screens.php

    diff --git src/bp-groups/bp-groups-screens.php src/bp-groups/bp-groups-screens.php
    index cbbc758..1e5bddb 100644
    function groups_screen_group_members() { 
    608608}
    609609
    610610/**
     611 * Handle the display of a group's subgroup directory.
     612 *
     613 * @since 2.7.0
     614 */
     615function groups_screen_subgroup_directory() {
     616
     617        if ( ! bp_is_single_item() )
     618                return false;
     619
     620        /**
     621         * Fires before the loading of a group's subgroups page.
     622         *
     623         * @since 2.7.0
     624         *
     625         * @param int $id ID of the group whose subgroups are being displayed.
     626         */
     627        do_action( 'groups_screen_subgroup_directory', bp_get_current_group_id() );
     628
     629        /**
     630         * Filters the template to load for a group's subgroups page.
     631         *
     632         * @since 2.7.0
     633         *
     634         * @param string $value Path to a group's subgroups template.
     635         */
     636        bp_core_load_template( apply_filters( 'groups_template_subgroup_directory', 'groups/single/home' ) );
     637}
     638
     639/**
    611640 * Handle the display of a group's Send Invites page.
    612641 *
    613642 * @since 1.0.0
    function groups_screen_group_admin_settings() { 
    911940                $allowed_invite_status = apply_filters( 'groups_allowed_invite_status', array( 'members', 'mods', 'admins' ) );
    912941                $invite_status         = isset( $_POST['group-invite-status'] ) && in_array( $_POST['group-invite-status'], (array) $allowed_invite_status ) ? $_POST['group-invite-status'] : 'members';
    913942
     943                // Set a parent group ID.
     944                $parent_id = ( isset( $_POST['parent-id'] ) ) ? (int) $_POST['parent-id'] : 0;
     945
    914946                // Check the nonce.
    915947                if ( !check_admin_referer( 'groups_edit_group_settings' ) )
    916948                        return false;
    917949
    918                 if ( !groups_edit_group_settings( $_POST['group-id'], $enable_forum, $status, $invite_status ) ) {
     950                if ( !groups_edit_group_settings( $_POST['group-id'], $enable_forum, $status, $invite_status, $parent_id ) ) {
    919951                        bp_core_add_message( __( 'There was an error updating group settings. Please try again.', 'buddypress' ), 'error' );
    920952                } else {
    921953                        bp_core_add_message( __( 'Group settings were successfully updated.', 'buddypress' ) );
  • src/bp-groups/bp-groups-template.php

    diff --git src/bp-groups/bp-groups-template.php src/bp-groups/bp-groups-template.php
    index f08fea5..3c12a5e 100644
    function bp_groups_directory_permalink() { 
    137137 *                                            about groups. Default: true.
    138138 *     @type array|string $exclude            Array or comma-separated list of group IDs. Results will exclude
    139139 *                                            the listed groups. Default: false.
     140 *     @type array|string $parent_id          Array or comma-separated list of group IDs. Results will include only
     141 *                                            child groups of the listed groups. Default: false.
    140142 *     @type bool         $update_meta_cache  Whether to fetch groupmeta for queried groups. Default: true.
    141143 * }
    142144 * @return bool True if there are groups to display that match the params
    function bp_has_groups( $args = '' ) { 
    150152        $slug         = false;
    151153        $type         = '';
    152154        $search_terms = false;
     155        $parent_id    = false;
    153156
    154157        // When looking your own groups, check for two action variables.
    155158        if ( bp_is_current_action( 'my-groups' ) ) {
    function bp_has_groups( $args = '' ) { 
    198201                'meta_query'         => false,
    199202                'include'            => false,
    200203                'exclude'            => false,
     204                'parent_id'          => $parent_id,
    201205                'populate_extras'    => true,
    202206                'update_meta_cache'  => true,
    203207        ), 'has_groups' );
    function bp_has_groups( $args = '' ) { 
    221225                'meta_query'         => $r['meta_query'],
    222226                'include'            => $r['include'],
    223227                'exclude'            => $r['exclude'],
     228                'parent_id'          => $r['parent_id'],
    224229                'populate_extras'    => (bool) $r['populate_extras'],
    225230                'update_meta_cache'  => (bool) $r['update_meta_cache'],
    226231        ) );
    function bp_group_permalink( $group = false ) { 
    770775        }
    771776
    772777/**
     778 * Output the permalink breadcrumbs for the current group in the loop.
     779 *
     780 * @since 2.7.0
     781 *
     782 * @param object|bool $group Optional. Group object.
     783 *                           Default: current group in loop.
     784 * @param string      $separator String to place between group links.
     785 */
     786function bp_group_permalink_breadcrumbs( $group = false, $separator = ' / ' ) {
     787        echo bp_get_group_permalink_breadcrumbs( $group, $separator );
     788}
     789        /**
     790         * Return the permalink breadcrumbs for the current group in the loop.
     791         *
     792         * @since 2.7.0
     793         *
     794         * @param object|bool $group Optional. Group object.
     795         *                           Default: current group in loop.
     796     * @param string      $separator String to place between group links.
     797     *
     798         * @return string
     799         */
     800        function bp_get_group_permalink_breadcrumbs( $group = false, $separator = ' / ' ) {
     801                global $groups_template;
     802
     803                if ( empty( $group ) ) {
     804                        $group = $groups_template->group;
     805                }
     806
     807                // Create the base group's entry.
     808                $item = '<a href="' . bp_get_group_permalink() . '">' . bp_get_group_name() . '</a>';
     809                $breadcrumbs = array( $item );
     810                $parent_id   = bp_groups_get_parent_group_id( $group->id, bp_loggedin_user_id() );
     811
     812                // Add breadcrumbs for the ancestors.
     813                while ( $parent_id ) {
     814                        $parent_group = groups_get_group( array( 'group_id' => $parent_id ) );
     815                        $breadcrumbs[] = '<a href="' . bp_get_group_permalink( $parent_group ) . '">' . bp_get_group_name( $parent_group ) . '</a>';
     816                        $parent_id   = bp_groups_get_parent_group_id( $parent_group->id, bp_loggedin_user_id() );
     817                }
     818
     819                $breadcrumbs = implode( $separator, array_reverse( $breadcrumbs ) );
     820
     821                /**
     822                 * Filters the permalink for the current group in the loop.
     823                 *
     824                 * @since 2.7.0
     825                 *
     826                 * @param string $breadcrumb String of breadcrumb links.
     827                 * @param object $group Group object.
     828                 */
     829                return apply_filters( 'bp_get_group_permalink_breadcrumbs', $breadcrumbs, $group );
     830        }
     831
     832/**
    773833 * Output the permalink for the admin section of the current group in the loop.
    774834 *
    775835 * @since 1.0.0
  • src/bp-groups/classes/class-bp-groups-component.php

    diff --git src/bp-groups/classes/class-bp-groups-component.php src/bp-groups/classes/class-bp-groups-component.php
    index 93bb9b7..811aa67 100644
    class BP_Groups_Component extends BP_Component { 
    603603                                );
    604604                        }
    605605
     606                        // Include the hierarchy item if there are groups to show or the user can create new subgroups.
     607                        if ( bp_current_user_can( 'create_subgroups', array( 'group_id' => $this->current_group->id ) ) ||
     608                                 bp_groups_has_children( $this->current_group->id, bp_loggedin_user_id() ) ) {
     609                                $sub_nav[] = array(
     610                                        'name'            => _x( 'Member Groups', 'Subgroup directory screen nav', 'buddypress' ),
     611                                        'slug'            => 'hierarchy',
     612                                        'parent_url'      => $group_link,
     613                                        'parent_slug'     => $this->current_group->slug,
     614                                        'screen_function' => 'groups_screen_subgroup_directory',
     615                                        'item_css_id'     => 'hierarchy',
     616                                        'position'        => 61,
     617                                        'user_has_access' => $this->current_group->user_has_access,
     618                                        'no_access_url'   => $group_link,
     619                                );
     620                        }
     621
    606622                        if ( bp_is_active( 'friends' ) && bp_groups_user_can_send_invites() ) {
    607623                                $sub_nav[] = array(
    608624                                        'name'            => _x( 'Send Invites', 'My Group screen nav', 'buddypress' ),
  • src/bp-groups/classes/class-bp-groups-group.php

    diff --git src/bp-groups/classes/class-bp-groups-group.php src/bp-groups/classes/class-bp-groups-group.php
    index bde4f1e..f16b8c3 100644
    class BP_Groups_Group { 
    6868        public $status;
    6969
    7070        /**
     71         * Parent ID.
     72         *
     73         * ID of parent group, if applicable.
     74         *
     75         * @since 2.7.0
     76         * @var int
     77         */
     78        public $parent_id;
     79
     80        /**
    7181         * Should (legacy) bbPress forums be enabled for this group?
    7282         *
    7383         * @since 1.6.0
    class BP_Groups_Group { 
    214224                $this->slug         = $group->slug;
    215225                $this->description  = stripslashes( $group->description );
    216226                $this->status       = $group->status;
     227                $this->parent_id    = $group->parent_id;
    217228                $this->enable_forum = $group->enable_forum;
    218229                $this->date_created = $group->date_created;
    219230
    class BP_Groups_Group { 
    282293                $this->slug         = apply_filters( 'groups_group_slug_before_save',         $this->slug,         $this->id );
    283294                $this->description  = apply_filters( 'groups_group_description_before_save',  $this->description,  $this->id );
    284295                $this->status       = apply_filters( 'groups_group_status_before_save',       $this->status,       $this->id );
     296                $this->parent_id    = apply_filters( 'groups_group_parent_id_before_save',    $this->parent_id,    $this->id );
    285297                $this->enable_forum = apply_filters( 'groups_group_enable_forum_before_save', $this->enable_forum, $this->id );
    286298                $this->date_created = apply_filters( 'groups_group_date_created_before_save', $this->date_created, $this->id );
    287299
    class BP_Groups_Group { 
    324336                                        slug = %s,
    325337                                        description = %s,
    326338                                        status = %s,
     339                                        parent_id = %d,
    327340                                        enable_forum = %d,
    328341                                        date_created = %s
    329342                                WHERE
    class BP_Groups_Group { 
    334347                                        $this->slug,
    335348                                        $this->description,
    336349                                        $this->status,
     350                                        $this->parent_id,
    337351                                        $this->enable_forum,
    338352                                        $this->date_created,
    339353                                        $this->id
    class BP_Groups_Group { 
    346360                                        slug,
    347361                                        description,
    348362                                        status,
     363                                        parent_id,
    349364                                        enable_forum,
    350365                                        date_created
    351366                                ) VALUES (
    352                                         %d, %s, %s, %s, %s, %d, %s
     367                                        %d, %s, %s, %s, %s, %d, %d, %s
    353368                                )",
    354369                                        $this->creator_id,
    355370                                        $this->name,
    356371                                        $this->slug,
    357372                                        $this->description,
    358373                                        $this->status,
     374                                        $this->parent_id,
    359375                                        $this->enable_forum,
    360376                                        $this->date_created
    361377                        );
    class BP_Groups_Group { 
    710726         *                                            See {@link WP_Meta_Query::queries} for description.
    711727         *     @type array|string $value              Optional. Array or comma-separated list of group IDs. Results
    712728         *                                            will be limited to groups within the list. Default: false.
     729         *     @type array|string $parent_id          Optional. Array or comma-separated list of group IDs. Results
     730         *                                            will be limited to children of the specified groups. Default: false.
    713731         *     @type bool         $populate_extras    Whether to fetch additional information
    714732         *                                            (such as member count) about groups. Default: true.
    715733         *     @type array|string $exclude            Optional. Array or comma-separated list of group IDs.
    class BP_Groups_Group { 
    761779                        'group_type__not_in' => '',
    762780                        'meta_query'         => false,
    763781                        'include'            => false,
     782                        'parent_id'          => false,
    764783                        'populate_extras'    => true,
    765784                        'update_meta_cache'  => true,
    766785                        'exclude'            => false,
    class BP_Groups_Group { 
    835854                        $sql['include'] = " AND g.id IN ({$include})";
    836855                }
    837856
     857                if ( ! empty( $r['parent_id'] ) ) {
     858                        $parent_id        = implode( ',', wp_parse_id_list( $r['parent_id'] ) );
     859                        $sql['parent_id'] = " AND g.parent_id IN ({$parent_id})";
     860                }
     861
    838862                if ( ! empty( $r['exclude'] ) ) {
    839863                        $exclude        = implode( ',', wp_parse_id_list( $r['exclude'] ) );
    840864                        $sql['exclude'] = " AND g.id NOT IN ({$exclude})";
    class BP_Groups_Group { 
    948972                }
    949973
    950974                // Already escaped in the paginated results block.
     975                if ( ! empty( $parent_id ) ) {
     976                        $total_sql['where'][] = "g.parent_id IN ({$parent_id})";
     977                }
     978
     979                // Already escaped in the paginated results block.
    951980                if ( ! empty( $exclude ) ) {
    952981                        $total_sql['where'][] = "g.id NOT IN ({$exclude})";
    953982                }
  • src/bp-groups/classes/class-bp-groups-template.php

    diff --git src/bp-groups/classes/class-bp-groups-template.php src/bp-groups/classes/class-bp-groups-template.php
    index 043d8f4..49e5eb6 100644
    class BP_Groups_Template { 
    165165                        'slug'               => false,
    166166                        'include'            => false,
    167167                        'exclude'            => false,
     168                        'parent_id'          => false,
    168169                        'search_terms'       => '',
    169170                        'group_type'         => '',
    170171                        'group_type__in'     => '',
    class BP_Groups_Template { 
    226227                                'group_type__not_in' => $group_type__not_in,
    227228                                'include'            => $include,
    228229                                'exclude'            => $exclude,
     230                                'parent_id'          => $parent_id,
    229231                                'populate_extras'    => $populate_extras,
    230232                                'update_meta_cache'  => $update_meta_cache,
    231233                                'show_hidden'        => $show_hidden,
  • src/bp-templates/bp-legacy/buddypress/groups/single/admin.php

    diff --git src/bp-templates/bp-legacy/buddypress/groups/single/admin.php src/bp-templates/bp-legacy/buddypress/groups/single/admin.php
    index abeab17..6898675 100644
    do_action( 'bp_before_group_admin_content' ); ?> 
    145145
    146146        <hr />
    147147
     148        <?php if ( bp_allow_hierarchical_groups() ) : ?>
     149
     150                <h4><?php _e( 'Group Hierarchy', 'buddypress' ); ?></h4>
     151
     152                <label for="parent_id">Parent Group</label>
     153                        <?php
     154                        $current_parent_group_id = bp_groups_get_parent_group_id();
     155                        $possible_parent_groups = bp_groups_get_possible_parent_groups( bp_get_current_group_id(), bp_loggedin_user_id() );
     156                        if ( $possible_parent_groups ) :
     157                                ?>
     158                                <select name="parent-id">
     159                                        <option value="0" <?php selected( 0, $current_parent_group_id ); ?>><?php echo _x( 'None selected', 'The option that sets a group to be a top-level group and have no parent.', 'buddypress' ); ?></option>
     160                                <?php foreach ( $possible_parent_groups as $possible_parent_group ) {
     161                                        ?>
     162                                        <option value="<?php echo $possible_parent_group->id; ?>" <?php selected( $current_parent_group_id, $possible_parent_group->id ); ?>><?php echo $possible_parent_group->name; ?></option>
     163                                        <?php
     164                                }
     165                                ?>
     166                                </select>
     167                                <?php
     168                        else :
     169                                ?>
     170                                <p><?php echo __( 'There are no groups available to be a parent to this group.', 'buddypress' ); ?></p>
     171                                <?php
     172                        endif;
     173                        ?>
     174
     175                <fieldset class="radio">
     176
     177                        <legend><?php _e( 'Which members of this group are allowed to create subgroups?', 'buddypress' ); ?></legend>
     178
     179                        <?php
     180                        $subgroup_creators = groups_get_groupmeta( bp_get_current_group_id(), 'allowed_subgroup_creators' );
     181                        if ( ! $subgroup_creators ) {
     182                                $subgroup_creators = 'noone';
     183                        }
     184                        ?>
     185
     186                        <label for="allowed-subgroup-creators-members"><input type="radio" name="allowed-subgroup-creators" id="allowed-subgroup-creators-members" value="member" <?php checked( $subgroup_creators, 'member' ); ?> /> <?php _e( 'All group members', 'buddypress' ); ?></label>
     187
     188                        <label for="allowed-subgroup-creators-mods"><input type="radio" name="allowed-subgroup-creators" id="allowed-subgroup-creators-mods" value="mod" <?php checked( $subgroup_creators, 'mod' ); ?> /> <?php _e( 'Group admins and mods only', 'buddypress' ); ?></label>
     189
     190                        <label for="allowed-subgroup-creators-admins"><input type="radio" name="allowed-subgroup-creators" id="allowed-subgroup-creators-admins" value="admin" <?php checked( $subgroup_creators, 'admin' ); ?> /> <?php _e( 'Group admins only', 'buddypress' ); ?></label>
     191
     192                        <label for="allowed-subgroup-creators-noone"><input type="radio" name="allowed-subgroup-creators" id="allowed-subgroup-creators-noone" value="noone" <?php checked( $subgroup_creators, 'noone' ); ?> /> <?php _e( 'No one', 'buddypress' ); ?></label>
     193                </fieldset>
     194
     195        <hr />
     196
     197        <?php endif; ?>
     198
    148199        <?php
    149200
    150201        /**
  • src/bp-templates/bp-legacy/buddypress/groups/single/home.php

    diff --git src/bp-templates/bp-legacy/buddypress/groups/single/home.php src/bp-templates/bp-legacy/buddypress/groups/single/home.php
    index 4fe26b8..72626ba 100644
     
    117117                                elseif ( bp_is_group_members()    ) : bp_groups_members_template_part();
    118118
    119119                                // Group Invitations
     120                                elseif ( bp_is_group_subgroups()  ) : bp_get_template_part( 'groups/single/subgroups-loop' );
     121
     122                                // Group Invitations
    120123                                elseif ( bp_is_group_invites()    ) : bp_get_template_part( 'groups/single/send-invites' );
    121124
    122125                                // Old group forums
  • new file src/bp-templates/bp-legacy/buddypress/groups/single/subgroups-loop.php

    diff --git src/bp-templates/bp-legacy/buddypress/groups/single/subgroups-loop.php src/bp-templates/bp-legacy/buddypress/groups/single/subgroups-loop.php
    new file mode 100644
    index 0000000..8bfb893
    - +  
     1<?php
     2/**
     3 * BuddyPress - Subgroups Loop
     4 *
     5 * Querystring is set via AJAX in _inc/ajax.php - bp_legacy_theme_object_filter().
     6 *
     7 * @package BuddyPress
     8 * @subpackage bp-legacy
     9 */
     10
     11?>
     12
     13<?php
     14// Store the groups template global so we can "reset" the loop after this subloop.
     15global $groups_template;
     16$parent_groups_template = $groups_template;
     17
     18// @TODO: Add a "create subgroup" button here?
     19
     20/**
     21 * Fires before the display of groups from the groups loop.
     22 *
     23 * @since 1.2.0
     24 */
     25do_action( 'bp_before_subgroups_loop' ); ?>
     26<?php
     27// @TODO: Hidden groups are excluded by default. Showing them all is dangerous.
     28// Will be much better when group capabilities are in place that allow granular exclusion of hidden groups.
     29// @TODO: Ajax pagination doesn't work.
     30?>
     31<?php if ( bp_has_groups( bp_ajax_querystring( 'groups' ) . '&type=alphabetical&parent_id=' . bp_get_current_group_id() ) ) : ?>
     32
     33        <div id="pag-top" class="pagination">
     34
     35                <div class="pag-count" id="group-dir-count-top">
     36
     37                        <?php bp_groups_pagination_count(); ?>
     38
     39                </div>
     40
     41                <div class="pagination-links" id="group-dir-pag-top">
     42
     43                        <?php bp_groups_pagination_links(); ?>
     44
     45                </div>
     46
     47        </div>
     48
     49        <?php
     50
     51        /**
     52         * Fires before the listing of the groups list.
     53         *
     54         * @since 1.1.0
     55         */
     56        do_action( 'bp_before_directory_subgroups_list' ); ?>
     57
     58        <ul id="groups-list" class="item-list">
     59
     60        <?php while ( bp_groups() ) : bp_the_group(); ?>
     61
     62                <li <?php bp_group_class(); ?>>
     63                        <?php if ( ! bp_disable_group_avatar_uploads() ) : ?>
     64                                <div class="item-avatar">
     65                                        <a href="<?php bp_group_permalink(); ?>"><?php bp_group_avatar( 'type=thumb&width=50&height=50' ); ?></a>
     66                                </div>
     67                        <?php endif; ?>
     68
     69                        <div class="item">
     70                                <div class="item-title"><a href="<?php bp_group_permalink(); ?>"><?php bp_group_name(); ?></a></div>
     71                                <div class="item-meta"><span class="activity"><?php printf( __( 'active %s', 'buddypress' ), bp_get_group_last_active() ); ?></span></div>
     72
     73                                <div class="item-desc"><?php bp_group_description_excerpt(); ?></div>
     74
     75                                <?php
     76
     77                                /**
     78                                 * Fires inside the listing of an individual group listing item.
     79                                 *
     80                                 * @since 1.1.0
     81                                 */
     82                                do_action( 'bp_directory_groups_item' ); ?>
     83
     84                        </div>
     85
     86                        <div class="action">
     87
     88                                <?php
     89
     90                                /**
     91                                 * Fires inside the action section of an individual group listing item.
     92                                 *
     93                                 * @since 1.1.0
     94                                 */
     95                                do_action( 'bp_directory_groups_actions' ); ?>
     96
     97                                <div class="meta">
     98
     99                                        <?php bp_group_type(); ?> / <?php bp_group_member_count(); ?>
     100
     101                                </div>
     102
     103                        </div>
     104
     105                        <div class="clear"></div>
     106                </li>
     107
     108        <?php endwhile; ?>
     109
     110        </ul>
     111
     112        <?php
     113
     114        /**
     115         * Fires after the listing of the groups list.
     116         *
     117         * @since 1.1.0
     118         */
     119        do_action( 'bp_after_directory_groups_list' ); ?>
     120
     121        <div id="pag-bottom" class="pagination">
     122
     123                <div class="pag-count" id="group-dir-count-bottom">
     124
     125                        <?php bp_groups_pagination_count(); ?>
     126
     127                </div>
     128
     129                <div class="pagination-links" id="group-dir-pag-bottom">
     130
     131                        <?php bp_groups_pagination_links(); ?>
     132
     133                </div>
     134
     135        </div>
     136
     137<?php else: ?>
     138
     139        <div id="message" class="info">
     140                <p><?php _e( 'There were no member groups found.', 'buddypress' ); ?></p>
     141        </div>
     142
     143<?php endif; ?>
     144
     145<?php
     146
     147/**
     148 * Fires after the display of groups from the groups loop.
     149 *
     150 * @since 1.2.0
     151 */
     152do_action( 'bp_after_subgroups_loop' );
     153
     154// Reset the $groups_template global and continue with the main group's
     155$groups_template = $parent_groups_template;
     156?>
  • tests/phpunit/testcases/groups/class-bp-groups-group.php

    diff --git tests/phpunit/testcases/groups/class-bp-groups-group.php tests/phpunit/testcases/groups/class-bp-groups-group.php
    index d42959c..67948b4 100644
    class BP_Tests_BP_Groups_Group_TestCases extends BP_UnitTestCase { 
    13831383
    13841384                $this->assertEmpty( $groups['groups'] );
    13851385        }
     1386
     1387        /**
     1388         * @group hierarchical_groups
     1389         */
     1390        public function test_get_by_parent_id() {
     1391                $g1 = $this->factory->group->create();
     1392                $g2 = $this->factory->group->create( array(
     1393                        'parent_id' => $g1,
     1394                ) );
     1395                $g3 = $this->factory->group->create( array(
     1396                        'parent_id' => $g2,
     1397                ) );
     1398                $g4 = $this->factory->group->create();
     1399
     1400                $groups = BP_Groups_Group::get( array(
     1401                        'parent_id' => $g1,
     1402                ) );
     1403
     1404                $found = wp_list_pluck( $groups['groups'], 'id' );
     1405                $this->assertEquals( array( $g2 ), $found );
     1406        }
     1407
     1408        /**
     1409         * @group hierarchical_groups
     1410         */
     1411        public function test_get_by_parent_id_ignore_grandparent() {
     1412                $g1 = $this->factory->group->create();
     1413                $g2 = $this->factory->group->create( array(
     1414                        'parent_id' => $g1,
     1415                ) );
     1416                $g3 = $this->factory->group->create( array(
     1417                        'parent_id' => $g2,
     1418                ) );
     1419                $g4 = $this->factory->group->create();
     1420
     1421                $groups = BP_Groups_Group::get( array(
     1422                        'parent_id' => $g2,
     1423                ) );
     1424
     1425                $found = wp_list_pluck( $groups['groups'], 'id' );
     1426                $this->assertEquals( array( $g3 ), $found );
     1427        }
     1428
     1429        /**
     1430         * @group hierarchical_groups
     1431         */
     1432        public function test_get_by_parent_id_array() {
     1433                $g1 = $this->factory->group->create();
     1434                $g2 = $this->factory->group->create( array(
     1435                        'parent_id' => $g1,
     1436                ) );
     1437                $g3 = $this->factory->group->create( array(
     1438                        'parent_id' => $g2,
     1439                ) );
     1440                $g4 = $this->factory->group->create();
     1441
     1442                $groups = BP_Groups_Group::get( array(
     1443                        'parent_id' => array( $g1, $g2 ),
     1444                ) );
     1445
     1446                $found = wp_list_pluck( $groups['groups'], 'id' );
     1447                $this->assertEqualSets( array( $g2, $g3 ), $found );
     1448        }
     1449
     1450        /**
     1451         * @group hierarchical_groups
     1452         */
     1453        public function test_get_by_parent_id_comma_separated_string() {
     1454                $g1 = $this->factory->group->create();
     1455                $g2 = $this->factory->group->create( array(
     1456                        'parent_id' => $g1,
     1457                ) );
     1458                $g3 = $this->factory->group->create( array(
     1459                        'parent_id' => $g2,
     1460                ) );
     1461                $g4 = $this->factory->group->create();
     1462
     1463                $groups = BP_Groups_Group::get( array(
     1464                        'parent_id' => "$g1, $g2",
     1465                ) );
     1466
     1467                $found = wp_list_pluck( $groups['groups'], 'id' );
     1468                $this->assertEqualSets( array( $g2, $g3 ), $found );
     1469        }
    13861470}
    13871471
    13881472/**
  • tests/phpunit/testcases/groups/functions.php

    diff --git tests/phpunit/testcases/groups/functions.php tests/phpunit/testcases/groups/functions.php
    index 22f4c53..eb098d2 100644
    Bar!'; 
    664664                groups_accept_invite( $u2, $g );
    665665                $this->assertEquals( 0, groups_get_invite_count_for_user( $u2 ) );
    666666        }
     667
     668
     669        /**
     670         * @group hierarchical_groups
     671         */
     672        public function test_update_orphaned_groups_on_group_delete_top_level() {
     673                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     674
     675                $g1 = $this->factory->group->create();
     676                $g2 = $this->factory->group->create( array(
     677                        'parent_id' => $g1,
     678                ) );
     679
     680                groups_delete_group( $g1 );
     681
     682                $child = groups_get_group( array( 'group_id' => $g2 ) );
     683                $this->assertEquals( 0, $child->parent_id );
     684                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     685        }
     686
     687        /**
     688         * @group hierarchical_groups
     689         */
     690        public function test_update_orphaned_groups_on_group_delete_two_levels() {
     691                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     692
     693                $g1 = $this->factory->group->create();
     694                $g2 = $this->factory->group->create( array(
     695                        'parent_id' => $g1,
     696                ) );
     697                $g3 = $this->factory->group->create( array(
     698                        'parent_id' => $g2,
     699                ) );
     700
     701                groups_delete_group( $g2 );
     702
     703                $child = groups_get_group( array( 'group_id' => $g3 ) );
     704                $this->assertEquals( $g1, $child->parent_id );
     705                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     706        }
     707
     708        /**
     709         * @group hierarchical_groups
     710         */
     711        public function test_bp_groups_get_child_groups_no_user_scope() {
     712                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     713
     714                $g1 = $this->factory->group->create();
     715                $g2 = $this->factory->group->create( array(
     716                        'parent_id' => $g1,
     717                ) );
     718                $g3 = $this->factory->group->create( array(
     719                        'parent_id' => $g1,
     720                        'status'    => 'hidden',
     721                ) );
     722
     723                $children = bp_groups_get_child_groups( $g1 );
     724                $found = wp_list_pluck( $children, 'id' );
     725
     726                $this->assertEqualSets( array( $g2, $g3), $found );
     727                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     728        }
     729
     730        /**
     731         * @group hierarchical_groups
     732         */
     733        public function test_bp_groups_get_child_groups_user_scope_logged_out() {
     734                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     735
     736                $g1 = $this->factory->group->create();
     737                $g2 = $this->factory->group->create( array(
     738                        'parent_id' => $g1,
     739                ) );
     740                $g3 = $this->factory->group->create( array(
     741                        'parent_id' => $g1,
     742                        'status'    => 'hidden',
     743                ) );
     744
     745                $children = bp_groups_get_child_groups( $g1, 0 );
     746                $found = wp_list_pluck( $children, 'id' );
     747
     748                $this->assertEqualSets( array( $g2 ), $found );
     749                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     750        }
     751
     752        /**
     753         * @group hierarchical_groups
     754         */
     755        public function test_bp_groups_get_child_groups_user_scope_not_group_member() {
     756                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     757
     758                $g1 = $this->factory->group->create();
     759                $g2 = $this->factory->group->create( array(
     760                        'parent_id' => $g1,
     761                ) );
     762                $g3 = $this->factory->group->create( array(
     763                        'parent_id' => $g1,
     764                        'status'    => 'hidden',
     765                ) );
     766                $u1 = $this->factory->user->create();
     767
     768                $children = bp_groups_get_child_groups( $g1, $u1 );
     769                $found = wp_list_pluck( $children, 'id' );
     770
     771                $this->assertEqualSets( array( $g2 ), $found );
     772                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     773        }
     774
     775        /**
     776         * @group hierarchical_groups
     777         */
     778        public function test_bp_groups_get_child_groups_user_scope_group_member() {
     779                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     780
     781                $u1 = $this->factory->user->create();
     782                $g1 = $this->factory->group->create();
     783                $g2 = $this->factory->group->create( array(
     784                        'parent_id' => $g1,
     785                ) );
     786                $g3 = $this->factory->group->create( array(
     787                        'parent_id'  => $g1,
     788                        'status'     => 'hidden',
     789                        'creator_id' => $u1,
     790                ) );
     791
     792                $children = bp_groups_get_child_groups( $g1, $u1 );
     793                $found = wp_list_pluck( $children, 'id' );
     794
     795                $this->assertEqualSets( array( $g2, $g3 ), $found );
     796                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     797        }
     798
     799        /**
     800         * @group hierarchical_groups
     801         */
     802        public function test_bp_groups_get_child_groups_user_scope_site_admin() {
     803                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     804
     805                $u1 = $this->factory->user->create( array( 'role' => 'administrator' ) );
     806                $g1 = $this->factory->group->create();
     807                $g2 = $this->factory->group->create( array(
     808                        'parent_id' => $g1,
     809                ) );
     810                $g3 = $this->factory->group->create( array(
     811                        'parent_id'  => $g1,
     812                        'status'     => 'hidden',
     813                ) );
     814
     815                $children = bp_groups_get_child_groups( $g1, $u1 );
     816                $found = wp_list_pluck( $children, 'id' );
     817
     818                $this->assertEqualSets( array( $g2, $g3 ), $found );
     819                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     820        }
     821
     822        /**
     823         * @group hierarchical_groups
     824         */
     825        public function test_bp_groups_get_descendent_groups_no_user_scope() {
     826                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     827
     828                $g1 = $this->factory->group->create();
     829                $g2 = $this->factory->group->create( array(
     830                        'parent_id' => $g1,
     831                ) );
     832                $g3 = $this->factory->group->create( array(
     833                        'parent_id' => $g1,
     834                        'status'    => 'hidden',
     835                ) );
     836                $g4 = $this->factory->group->create( array(
     837                        'parent_id' => $g2,
     838                ) );
     839                $g5 = $this->factory->group->create();
     840
     841                $children = bp_groups_get_descendent_groups( $g1 );
     842                $found = wp_list_pluck( $children, 'id' );
     843
     844                $this->assertEqualSets( array( $g2, $g3, $g4 ), $found );
     845                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     846        }
     847
     848        /**
     849         * @group hierarchical_groups
     850         */
     851        public function test_bp_groups_get_descendent_groups_user_scope_logged_out() {
     852                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     853
     854                $g1 = $this->factory->group->create();
     855                $g2 = $this->factory->group->create( array(
     856                        'parent_id' => $g1,
     857                ) );
     858                $g3 = $this->factory->group->create( array(
     859                        'parent_id' => $g1,
     860                        'status'    => 'hidden',
     861                ) );
     862                $g4 = $this->factory->group->create( array(
     863                        'parent_id' => $g2,
     864                ) );
     865                $g5 = $this->factory->group->create();
     866
     867                $children = bp_groups_get_descendent_groups( $g1, 0 );
     868                $found = wp_list_pluck( $children, 'id' );
     869
     870                $this->assertEqualSets( array( $g2, $g4 ), $found );
     871                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     872        }
     873
     874        /**
     875         * @group hierarchical_groups
     876         */
     877        public function test_bp_groups_get_descendent_groups_user_scope_not_group_member() {
     878                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     879
     880                $g1 = $this->factory->group->create();
     881                $g2 = $this->factory->group->create( array(
     882                        'parent_id' => $g1,
     883                ) );
     884                $g3 = $this->factory->group->create( array(
     885                        'parent_id' => $g1,
     886                        'status'    => 'hidden',
     887                ) );
     888                $g4 = $this->factory->group->create( array(
     889                        'parent_id' => $g2,
     890                ) );
     891                $g5 = $this->factory->group->create();
     892                $u1 = $this->factory->user->create();
     893
     894                $children = bp_groups_get_descendent_groups( $g1, $u1 );
     895                $found = wp_list_pluck( $children, 'id' );
     896
     897                $this->assertEqualSets( array( $g2, $g4 ), $found );
     898                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     899        }
     900
     901        /**
     902         * @group hierarchical_groups
     903         */
     904        public function test_bp_groups_get_descendent_groups_user_scope_group_member() {
     905                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     906
     907                $u1 = $this->factory->user->create();
     908                $g1 = $this->factory->group->create();
     909                $g2 = $this->factory->group->create( array(
     910                        'parent_id' => $g1,
     911                ) );
     912                $g3 = $this->factory->group->create( array(
     913                        'parent_id'  => $g1,
     914                        'status'     => 'hidden',
     915                        'creator_id' => $u1,
     916                ) );
     917                $g4 = $this->factory->group->create( array(
     918                        'parent_id' => $g2,
     919                ) );
     920                $g5 = $this->factory->group->create();
     921
     922                $children = bp_groups_get_descendent_groups( $g1, $u1 );
     923                $found = wp_list_pluck( $children, 'id' );
     924
     925                $this->assertEqualSets( array( $g2, $g3, $g4 ), $found );
     926                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     927        }
     928
     929        /**
     930         * @group hierarchical_groups
     931         */
     932        public function test_bp_groups_get_descendent_groups_user_scope_site_admin() {
     933                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     934
     935                $u1 = $this->factory->user->create( array( 'role' => 'administrator' ) );
     936                $this->grant_super_admin( $u1 );
     937                $g1 = $this->factory->group->create();
     938                $g2 = $this->factory->group->create( array(
     939                        'parent_id' => $g1,
     940                ) );
     941                $g3 = $this->factory->group->create( array(
     942                        'parent_id'  => $g1,
     943                        'status'     => 'hidden',
     944                ) );
     945                $g4 = $this->factory->group->create( array(
     946                        'parent_id' => $g2,
     947                ) );
     948                $g5 = $this->factory->group->create();
     949
     950                $children = bp_groups_get_descendent_groups( $g1, $u1 );
     951                $found = wp_list_pluck( $children, 'id' );
     952
     953                $this->assertEqualSets( array( $g2, $g3, $g4 ), $found );
     954                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     955        }
     956
     957        /**
     958         * @group hierarchical_groups
     959         */
     960        public function test_bp_groups_get_ancestor_group_ids_no_user_scope() {
     961                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     962
     963                $g1 = $this->factory->group->create();
     964                $g2 = $this->factory->group->create( array(
     965                        'parent_id' => $g1,
     966                ) );
     967                $g3 = $this->factory->group->create( array(
     968                        'parent_id' => $g1,
     969                        'status'    => 'hidden',
     970                ) );
     971                $g4 = $this->factory->group->create( array(
     972                        'parent_id' => $g2,
     973                ) );
     974                $g5 = $this->factory->group->create();
     975
     976                $children = bp_groups_get_ancestor_group_ids( $g4 );
     977
     978                $this->assertEqualSets( array( $g1, $g2 ), $children );
     979                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     980        }
     981
     982        /**
     983         * @group hierarchical_groups
     984         */
     985        public function test_bp_groups_get_ancestor_group_ids_user_scope_logged_out() {
     986                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     987
     988                $g1 = $this->factory->group->create();
     989                $g2 = $this->factory->group->create( array(
     990                        'parent_id' => $g1,
     991                ) );
     992                $g3 = $this->factory->group->create( array(
     993                        'parent_id' => $g1,
     994                        'status'    => 'hidden',
     995                ) );
     996                $g4 = $this->factory->group->create( array(
     997                        'parent_id' => $g2,
     998                ) );
     999                $g5 = $this->factory->group->create();
     1000
     1001                $children = bp_groups_get_ancestor_group_ids( $g4, 0 );
     1002
     1003                $this->assertEqualSets( array( $g1, $g2 ), $children );
     1004                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1005        }
     1006
     1007        /**
     1008         * @group hierarchical_groups
     1009         */
     1010        public function test_bp_groups_get_ancestor_group_ids_user_scope_logged_out_w_hidden() {
     1011                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1012
     1013                $g1 = $this->factory->group->create();
     1014                $g2 = $this->factory->group->create( array(
     1015                        'parent_id' => $g1,
     1016                ) );
     1017                $g3 = $this->factory->group->create( array(
     1018                        'parent_id' => $g1,
     1019                        'status'    => 'hidden',
     1020                ) );
     1021                $g4 = $this->factory->group->create( array(
     1022                        'parent_id' => $g3,
     1023                ) );
     1024                $g5 = $this->factory->group->create();
     1025
     1026                $children = bp_groups_get_ancestor_group_ids( $g4, 0 );
     1027
     1028                $this->assertEqualSets( array(), $children );
     1029                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1030        }
     1031
     1032        /**
     1033         * @group hierarchical_groups
     1034         */
     1035        public function test_bp_groups_get_ancestor_group_ids_user_scope_not_group_member() {
     1036                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1037
     1038                $g1 = $this->factory->group->create( array(
     1039                        'status' => 'hidden',
     1040                ) );
     1041                $g2 = $this->factory->group->create( array(
     1042                        'parent_id' => $g1,
     1043                ) );
     1044                $g3 = $this->factory->group->create( array(
     1045                        'parent_id' => $g1,
     1046                ) );
     1047                $g4 = $this->factory->group->create( array(
     1048                        'parent_id' => $g3,
     1049                ) );
     1050                $g5 = $this->factory->group->create();
     1051                $u1 = $this->factory->user->create();
     1052
     1053                $children = bp_groups_get_ancestor_group_ids( $g4, $u1 );
     1054
     1055                $this->assertEqualSets( array( $g3 ), $children );
     1056                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1057        }
     1058
     1059        /**
     1060         * @group hierarchical_groups
     1061         */
     1062        public function test_bp_groups_get_ancestor_group_ids_user_scope_group_member() {
     1063                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1064
     1065                $u1 = $this->factory->user->create();
     1066                $g1 = $this->factory->group->create();
     1067                $g2 = $this->factory->group->create( array(
     1068                        'parent_id' => $g1,
     1069                ) );
     1070                $g3 = $this->factory->group->create( array(
     1071                        'parent_id'  => $g1,
     1072                        'status'     => 'hidden',
     1073                        'creator_id' => $u1,
     1074                ) );
     1075                $g4 = $this->factory->group->create( array(
     1076                        'parent_id' => $g3,
     1077                ) );
     1078                $g5 = $this->factory->group->create();
     1079
     1080                $children = bp_groups_get_ancestor_group_ids( $g4, $u1 );
     1081
     1082                $this->assertEqualSets( array( $g1, $g3 ), $children );
     1083                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1084        }
     1085
     1086        /**
     1087         * @group hierarchical_groups
     1088         */
     1089        public function test_bp_groups_get_ancestor_group_ids_user_scope_site_admin() {
     1090                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1091
     1092                $u1 = $this->factory->user->create( array( 'role' => 'administrator' ) );
     1093                $this->grant_super_admin( $u1 );
     1094                $g1 = $this->factory->group->create();
     1095                $g2 = $this->factory->group->create( array(
     1096                        'parent_id' => $g1,
     1097                ) );
     1098                $g3 = $this->factory->group->create( array(
     1099                        'parent_id'  => $g1,
     1100                        'status'     => 'hidden',
     1101                ) );
     1102                $g4 = $this->factory->group->create( array(
     1103                        'parent_id' => $g3,
     1104                ) );
     1105                $g5 = $this->factory->group->create();
     1106
     1107                $children = bp_groups_get_ancestor_group_ids( $g4, $u1 );
     1108
     1109                $this->assertEqualSets( array( $g1, $g3 ), $children );
     1110                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1111        }
     1112
     1113        /**
     1114         * @group hierarchical_groups
     1115         */
     1116        public function test_bp_groups_get_possible_parent_groups_user_is_nonmember_member_allowed() {
     1117                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1118
     1119                $u1 = $this->factory->user->create();
     1120                $g1 = $this->factory->group->create();
     1121                $g2 = $this->factory->group->create( array(
     1122                        'parent_id' => $g1,
     1123                ) );
     1124                $g3 = $this->factory->group->create( array(
     1125                        'parent_id'  => $g1,
     1126                ) );
     1127                $g4 = $this->factory->group->create( array(
     1128                        'parent_id' => $g3,
     1129                ) );
     1130                $g5 = $this->factory->group->create();
     1131                $g6 = $this->factory->group->create();
     1132
     1133                // Members can create subgroups.
     1134                groups_update_groupmeta( $g6, 'allowed_subgroup_creators', 'member' );
     1135
     1136                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     1137                $found = wp_list_pluck( $groups, 'id' );
     1138
     1139                $this->assertEqualSets( array(), $found );
     1140                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1141        }
     1142
     1143        /**
     1144         * @group hierarchical_groups_one
     1145         */
     1146        public function test_bp_groups_get_possible_parent_groups_user_is_member_member_allowed() {
     1147                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1148
     1149                $u1 = $this->factory->user->create();
     1150                $g1 = $this->factory->group->create();
     1151                $g2 = $this->factory->group->create( array(
     1152                        'parent_id' => $g1,
     1153                ) );
     1154                $g3 = $this->factory->group->create( array(
     1155                        'parent_id'  => $g1,
     1156                ) );
     1157                $g4 = $this->factory->group->create( array(
     1158                        'parent_id' => $g3,
     1159                ) );
     1160                $g5 = $this->factory->group->create();
     1161                $g6 = $this->factory->group->create();
     1162
     1163                // Make $u1 a member.
     1164                $this->add_user_to_group( $u1, $g6 );
     1165
     1166                // Members can create subgroups.
     1167                groups_add_groupmeta( $g6, 'allowed_subgroup_creators', 'member' );
     1168
     1169                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     1170                $found = wp_list_pluck( $groups, 'id' );
     1171
     1172                $this->assertEqualSets( array( $g6 ), $found );
     1173
     1174                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1175        }
     1176
     1177        /**
     1178         * @group hierarchical_groups
     1179         */
     1180        public function test_bp_groups_get_possible_parent_groups_user_is_member_mod_allowed() {
     1181                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1182
     1183                $u1 = $this->factory->user->create();
     1184                $g1 = $this->factory->group->create();
     1185                $g2 = $this->factory->group->create( array(
     1186                        'parent_id' => $g1,
     1187                ) );
     1188                $g3 = $this->factory->group->create( array(
     1189                        'parent_id'  => $g1,
     1190                ) );
     1191                $g4 = $this->factory->group->create( array(
     1192                        'parent_id' => $g3,
     1193                ) );
     1194                $g5 = $this->factory->group->create();
     1195                $g6 = $this->factory->group->create();
     1196
     1197                // Make $u1 a member.
     1198                $this->add_user_to_group( $u1, $g6 );
     1199
     1200                // Members can create subgroups.
     1201                groups_update_groupmeta( $g6, 'allowed_subgroup_creators', 'mod' );
     1202
     1203                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     1204                $found = wp_list_pluck( $groups, 'id' );
     1205
     1206                $this->assertEqualSets( array(), $found );
     1207                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1208        }
     1209
     1210        /**
     1211         * @group hierarchical_groups
     1212         */
     1213        public function test_bp_groups_get_possible_parent_groups_user_is_mod_mod_allowed() {
     1214                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1215
     1216                $u1 = $this->factory->user->create();
     1217                $g1 = $this->factory->group->create();
     1218                $g2 = $this->factory->group->create( array(
     1219                        'parent_id' => $g1,
     1220                ) );
     1221                $g3 = $this->factory->group->create( array(
     1222                        'parent_id'  => $g1,
     1223                ) );
     1224                $g4 = $this->factory->group->create( array(
     1225                        'parent_id' => $g3,
     1226                ) );
     1227                $g5 = $this->factory->group->create();
     1228                $g6 = $this->factory->group->create();
     1229
     1230                // Make $u1 a member, then promote.
     1231                $this->add_user_to_group( $u1, $g6 );
     1232                $m1 = new BP_Groups_Member( $u1, $g6 );
     1233                $m1->promote( 'mod' );
     1234
     1235                // Members can create subgroups.
     1236                groups_update_groupmeta( $g6, 'allowed_subgroup_creators', 'mod' );
     1237
     1238                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     1239                $found = wp_list_pluck( $groups, 'id' );
     1240
     1241                $this->assertEqualSets( array( $g6 ), $found );
     1242                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1243        }
     1244
     1245        /**
     1246         * @group hierarchical_groups
     1247         */
     1248        public function test_bp_groups_get_possible_parent_groups_user_is_mod_admin_allowed() {
     1249                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1250
     1251                $u1 = $this->factory->user->create();
     1252                $g1 = $this->factory->group->create();
     1253                $g2 = $this->factory->group->create( array(
     1254                        'parent_id' => $g1,
     1255                ) );
     1256                $g3 = $this->factory->group->create( array(
     1257                        'parent_id'  => $g1,
     1258                ) );
     1259                $g4 = $this->factory->group->create( array(
     1260                        'parent_id' => $g3,
     1261                ) );
     1262                $g5 = $this->factory->group->create();
     1263                $g6 = $this->factory->group->create();
     1264
     1265                // Make $u1 a member, then promote.
     1266                $this->add_user_to_group( $u1, $g6 );
     1267                $m1 = new BP_Groups_Member( $u1, $g6 );
     1268                $m1->promote( 'mod' );
     1269
     1270                // Members can create subgroups.
     1271                groups_update_groupmeta( $g6, 'allowed_subgroup_creators', 'admin' );
     1272
     1273                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     1274                $found = wp_list_pluck( $groups, 'id' );
     1275
     1276                $this->assertEqualSets( array(), $found );
     1277                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1278        }
     1279
     1280        /**
     1281         * @group hierarchical_groups
     1282         */
     1283        public function test_bp_groups_get_possible_parent_groups_user_is_admin_admin_allowed() {
     1284                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1285
     1286                $u1 = $this->factory->user->create();
     1287                $g1 = $this->factory->group->create();
     1288                $g2 = $this->factory->group->create( array(
     1289                        'parent_id' => $g1,
     1290                ) );
     1291                $g3 = $this->factory->group->create( array(
     1292                        'parent_id'  => $g1,
     1293                ) );
     1294                $g4 = $this->factory->group->create( array(
     1295                        'parent_id' => $g3,
     1296                ) );
     1297                $g5 = $this->factory->group->create();
     1298                $g6 = $this->factory->group->create();
     1299
     1300                // Make $u1 a member, then promote.
     1301                $this->add_user_to_group( $u1, $g6 );
     1302                $m1 = new BP_Groups_Member( $u1, $g6 );
     1303                $m1->promote( 'admin' );
     1304
     1305                // Members can create subgroups.
     1306                groups_update_groupmeta( $g6, 'allowed_subgroup_creators', 'admin' );
     1307
     1308                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     1309                $found = wp_list_pluck( $groups, 'id' );
     1310
     1311                $this->assertEqualSets( array( $g6 ), $found );
     1312                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1313        }
     1314
     1315        /**
     1316         * @group hierarchical_groups
     1317         */
     1318        public function test_bp_groups_get_possible_parent_groups_user_is_admin_noone_allowed() {
     1319                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1320
     1321                $u1 = $this->factory->user->create();
     1322                $g1 = $this->factory->group->create();
     1323                $g2 = $this->factory->group->create( array(
     1324                        'parent_id' => $g1,
     1325                ) );
     1326                $g3 = $this->factory->group->create( array(
     1327                        'parent_id'  => $g1,
     1328                ) );
     1329                $g4 = $this->factory->group->create( array(
     1330                        'parent_id' => $g3,
     1331                ) );
     1332                $g5 = $this->factory->group->create();
     1333                $g6 = $this->factory->group->create();
     1334
     1335                // Make $u1 a member, then promote.
     1336                $this->add_user_to_group( $u1, $g6 );
     1337                $m1 = new BP_Groups_Member( $u1, $g6 );
     1338                $m1->promote( 'admin' );
     1339
     1340                // Members can create subgroups.
     1341                groups_update_groupmeta( $g6, 'allowed_subgroup_creators', 'noone' );
     1342
     1343                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     1344                $found = wp_list_pluck( $groups, 'id' );
     1345
     1346                $this->assertEqualSets( array(), $found );
     1347                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1348        }
     1349
     1350        /**
     1351         * @group hierarchical_groups
     1352         */
     1353        public function test_bp_groups_get_possible_parent_groups_user_scope_site_admin() {
     1354                add_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1355
     1356                $u1 = $this->factory->user->create( array( 'role' => 'administrator' ) );
     1357                $this->grant_super_admin( $u1 );
     1358                $g1 = $this->factory->group->create();
     1359                $g2 = $this->factory->group->create( array(
     1360                        'parent_id' => $g1,
     1361                ) );
     1362                $g3 = $this->factory->group->create( array(
     1363                        'parent_id'  => $g1,
     1364                ) );
     1365                $g4 = $this->factory->group->create( array(
     1366                        'parent_id' => $g3,
     1367                ) );
     1368                $g5 = $this->factory->group->create();
     1369                $g6 = $this->factory->group->create();
     1370
     1371                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     1372                $found = wp_list_pluck( $groups, 'id' );
     1373
     1374                $this->assertEqualSets( array( $g5, $g6 ), $found );
     1375                remove_filter( 'bp_allow_hierarchical_groups', '__return_true' );
     1376        }
     1377
    6671378}