Skip to:
Content

BuddyPress.org

Ticket #3961: 3961.02.patch

File 3961.02.patch, 75.7 KB (added by dcavins, 3 years ago)

Second round.

  • 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..4fc8e2f 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/admin/css/admin.css

    diff --git src/bp-groups/admin/css/admin.css src/bp-groups/admin/css/admin.css
    index 651a6d5..5016830 100644
    body.toplevel_page_bp-groups table.groups th#last_active { 
    3232        margin: 10px 0;
    3333        font-weight: bold;
    3434}
     35.bp-groups-settings-section .section-header {
     36        display: block;
     37        margin: 10px 0;
     38        font-weight: bold;
     39}
    3540#bp_group_settings ul {
    3641        margin: 0;
    3742}
  • 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..f2b3719 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-parent">
     834                        <label for="parent-id" class="section-header"><?php _e( 'Parent Group', 'buddypress' ); ?></label>
     835                        <?php
     836                        $current_parent_group_id = bp_groups_get_parent_group_id( $item->id );
     837                        $possible_parent_groups = bp_groups_get_possible_parent_groups( $item->id, bp_loggedin_user_id() );
     838                        if ( $possible_parent_groups ) :
     839                                ?>
     840                                <select name="parent-id" id="parent-id">
     841                                        <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>
     842                                <?php foreach ( $possible_parent_groups as $possible_parent_group ) {
     843                                        ?>
     844                                        <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>
     845                                        <?php
     846                                }
     847                                ?>
     848                                </select>
     849                                <?php
     850                        else :
     851                                ?>
     852                                <p><?php echo __( 'There are no groups available to be a parent to this group.', 'buddypress' ); ?></p>
     853                                <?php
     854                        endif;
     855                        ?>
     856                </div>
     857
     858                <div class="bp-groups-settings-section" id="bp-groups-settings-section-group-hierarchy-creator">
     859                        <fieldset>
     860                                <legend><?php _e( 'Which members of this group are allowed to create subgroups?', 'buddypress' ); ?></legend>
     861
     862                                <?php
     863                                $subgroup_creators = groups_get_groupmeta( $item->id, 'allowed_subgroup_creators' );
     864                                if ( ! $subgroup_creators ) {
     865                                        $subgroup_creators = 'noone';
     866                                }
     867                                ?>
     868                                <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>
     869                                <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>
     870                                <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>
     871                                <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>
     872                        </fieldset>
     873                </div>
     874        <?php endif; ?>
    829875<?php
    830876}
    831877
  • 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..1699b15 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 group creation is restricted, respect that setting.
     392        if ( bp_restrict_group_creation() && ! bp_user_can( $user_id, 'bp_moderate' ) ) {
     393                return false;
     394        }
     395
     396        // If hierarchical groups are not allowed, stop now.
     397        if ( ! bp_allow_hierarchical_groups() ) {
     398                return false;
     399        }
     400
     401        // We need to know which group is in question.
     402        if ( empty( $args['group_id'] ) ) {
     403                return false;
     404        }
     405        $group_id = (int) $args['group_id'];
     406
     407        // Possible settings for the group meta setting 'allowed_subgroup_creators'
     408        $creator_setting = groups_get_groupmeta( $group_id, 'allowed_subgroup_creators' );
     409        switch ( $creator_setting ) {
     410                case 'admin' :
     411                        $retval = groups_is_user_admin( $user_id, $group_id );
     412                        break;
     413
     414                case 'mod' :
     415                        $retval = ( groups_is_user_mod( $user_id, $group_id ) ||
     416                                                groups_is_user_admin( $user_id, $group_id ) );
     417                        break;
     418
     419                case 'member' :
     420                        $retval = groups_is_user_member( $user_id, $group_id );
     421                        break;
     422
     423                case 'noone' :
     424                default :
     425                        // @TODO: This seems weird, but I can imagine situations where only site admins should be able to associate groups.
     426                        $retval = bp_user_can( $user_id, 'bp_moderate' );
     427                        break;
     428        }
     429
     430        return $retval;
     431}
     432add_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..707af2d 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        $args = array(
     2741                'orderby'         => 'name',
     2742                'order'           => 'ASC',
     2743                'populate_extras' => false,
     2744                'exclude'         => $exclude_ids, // Exclude descendants and this group.
     2745                'show_hidden'     => true,
     2746                'per_page'        => false, // Do not limit the number returned.
     2747                'page'            => false, // Do not limit the number returned.
     2748        );
     2749        // If the user is not a site admin, limit the set to groups she belongs to.
     2750        if ( ! bp_user_can( $user_id, 'bp_moderate' ) ) {
     2751                $args['user_id'] = $user_id;
     2752        }
     2753        $possible_parents = groups_get_groups( $args );
     2754        foreach ( $possible_parents['groups'] as $k => $group ) {
     2755                // Check whether the user can create child groups of this group.
     2756                if ( ! bp_user_can( $user_id, 'create_subgroups', array( 'group_id' => $group->id ) ) ) {
     2757                        unset( $possible_parents['groups'][$k] );
     2758                }
     2759        }
     2760
     2761        return $possible_parents['groups'];
     2762}
  • 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..53201a8 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..b075ddd 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_groups_has_children( $this->current_group->id, bp_loggedin_user_id() ) ) {
     608                                $sub_nav[] = array(
     609                                        'name'            => _x( 'Subgroups', 'Subgroup directory screen nav', 'buddypress' ),
     610                                        'slug'            => 'hierarchy',
     611                                        'parent_url'      => $group_link,
     612                                        'parent_slug'     => $this->current_group->slug,
     613                                        'screen_function' => 'groups_screen_subgroup_directory',
     614                                        'item_css_id'     => 'hierarchy',
     615                                        'position'        => 61,
     616                                        'user_has_access' => $this->current_group->user_has_access,
     617                                        'no_access_url'   => $group_link,
     618                                );
     619                        }
     620
    606621                        if ( bp_is_active( 'friends' ) && bp_groups_user_can_send_invites() ) {
    607622                                $sub_nav[] = array(
    608623                                        '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/create.php

    diff --git src/bp-templates/bp-legacy/buddypress/groups/create.php src/bp-templates/bp-legacy/buddypress/groups/create.php
    index 244b0fc..0c33052 100644
    do_action( 'bp_before_create_group_page' ); ?> 
    144144
    145145                                </div>
    146146
     147                                <?php if ( bp_allow_hierarchical_groups() ) : ?>
     148
     149                                        <h4><?php _e( 'Group Hierarchy', 'buddypress' ); ?></h4>
     150
     151                                        <label for="parent-id">Parent Group</label>
     152                                                <?php
     153                                                $current_parent_group_id = bp_groups_get_parent_group_id();
     154                                                $possible_parent_groups = bp_groups_get_possible_parent_groups( bp_get_current_group_id(), bp_loggedin_user_id() );
     155                                                if ( $possible_parent_groups ) :
     156                                                        ?>
     157                                                        <select id="parent-id" name="parent-id">
     158                                                                <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>
     159                                                        <?php foreach ( $possible_parent_groups as $possible_parent_group ) {
     160                                                                ?>
     161                                                                <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>
     162                                                                <?php
     163                                                        }
     164                                                        ?>
     165                                                        </select>
     166                                                        <?php
     167                                                else :
     168                                                        ?>
     169                                                        <p><?php _e( 'There are no groups available to be a parent to this group.', 'buddypress' ); ?></p>
     170                                                        <?php
     171                                                endif;
     172                                                ?>
     173
     174                                        <fieldset class="radio">
     175
     176                                                <legend><?php _e( 'Which members of this group are allowed to create subgroups?', 'buddypress' ); ?></legend>
     177
     178                                                <?php
     179                                                $subgroup_creators = groups_get_groupmeta( bp_get_current_group_id(), 'allowed_subgroup_creators' );
     180                                                if ( ! $subgroup_creators ) {
     181                                                        $subgroup_creators = 'noone';
     182                                                }
     183                                                ?>
     184
     185                                                <?php
     186                                                // If only site admins can create groups, don't display impossible options.
     187                                                if ( ! bp_restrict_group_creation() ) :
     188                                                ?>
     189                                                        <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>
     190
     191                                                        <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>
     192
     193                                                        <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>
     194                                                <?php endif; ?>
     195
     196                                                <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>
     197                                        </fieldset>
     198
     199                                <?php endif; ?>
     200
    147201                                <?php if ( bp_is_active( 'forums' ) ) : ?>
    148202
    149203                                        <h4><?php _e( 'Group Forums', 'buddypress' ); ?></h4>
  • src/bp-templates/bp-legacy/buddypress/groups/groups-loop.php

    diff --git src/bp-templates/bp-legacy/buddypress/groups/groups-loop.php src/bp-templates/bp-legacy/buddypress/groups/groups-loop.php
    index a988259..e9100e4 100644
    do_action( 'bp_before_groups_loop' ); ?> 
    6363
    6464                                <div class="item-desc"><?php bp_group_description_excerpt(); ?></div>
    6565
     66                                <?php if ( bp_allow_hierarchical_groups() ) :?>
     67                                        <div class="group-hierarchy-breadcrumbs"><?php bp_group_permalink_breadcrumbs(); ?></div>
     68                                <?php endif; ?>
     69
    6670                                <?php
    6771
    6872                                /**
  • 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..9388f46 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 id="parent-id" 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 _e( '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                        <?php
     187                        // If only site admins can create groups, don't display impossible options.
     188                        if ( ! bp_restrict_group_creation() ) :
     189                        ?>
     190                                <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>
     191
     192                                <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>
     193
     194                                <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>
     195                        <?php endif; ?>
     196
     197                        <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>
     198                </fieldset>
     199
     200        <hr />
     201
     202        <?php endif; ?>
     203
    148204        <?php
    149205
    150206        /**
  • 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..1b2a38b
    - +  
     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 if ( bp_allow_hierarchical_groups() ) :?>
     76                                        <div class="group-hierarchy-breadcrumbs"><?php bp_group_permalink_breadcrumbs(); ?></div>
     77                                <?php endif; ?>
     78
     79                                <?php
     80
     81                                /**
     82                                 * Fires inside the listing of an individual group listing item.
     83                                 *
     84                                 * @since 1.1.0
     85                                 */
     86                                do_action( 'bp_directory_groups_item' ); ?>
     87
     88                        </div>
     89
     90                        <div class="action">
     91
     92                                <?php
     93
     94                                /**
     95                                 * Fires inside the action section of an individual group listing item.
     96                                 *
     97                                 * @since 1.1.0
     98                                 */
     99                                do_action( 'bp_directory_groups_actions' ); ?>
     100
     101                                <div class="meta">
     102
     103                                        <?php bp_group_type(); ?> / <?php bp_group_member_count(); ?>
     104
     105                                </div>
     106
     107                        </div>
     108
     109                        <div class="clear"></div>
     110                </li>
     111
     112        <?php endwhile; ?>
     113
     114        </ul>
     115
     116        <?php
     117
     118        /**
     119         * Fires after the listing of the groups list.
     120         *
     121         * @since 1.1.0
     122         */
     123        do_action( 'bp_after_directory_groups_list' ); ?>
     124
     125        <div id="pag-bottom" class="pagination">
     126
     127                <div class="pag-count" id="group-dir-count-bottom">
     128
     129                        <?php bp_groups_pagination_count(); ?>
     130
     131                </div>
     132
     133                <div class="pagination-links" id="group-dir-pag-bottom">
     134
     135                        <?php bp_groups_pagination_links(); ?>
     136
     137                </div>
     138
     139        </div>
     140
     141<?php else: ?>
     142
     143        <div id="message" class="info">
     144                <p><?php _e( 'There were no subgroups found.', 'buddypress' ); ?></p>
     145        </div>
     146
     147<?php endif; ?>
     148
     149<?php
     150
     151/**
     152 * Fires after the display of groups from the groups loop.
     153 *
     154 * @since 1.2.0
     155 */
     156do_action( 'bp_after_subgroups_loop' );
     157
     158// Reset the $groups_template global and continue with the main group's
     159$groups_template = $parent_groups_template;
     160?>
  • 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/**
  • new file tests/phpunit/testcases/groups/class-bp-groups-hierarchy.php

    diff --git tests/phpunit/testcases/groups/class-bp-groups-hierarchy.php tests/phpunit/testcases/groups/class-bp-groups-hierarchy.php
    new file mode 100644
    index 0000000..ff6daa9
    - +  
     1<?php
     2
     3/**
     4 * @group groups
     5 * @group functions
     6 * @group hierarchical_groups
     7 */
     8class BP_Tests_Hierarchical_Groups extends BP_UnitTestCase {
     9
     10        public function setUp() {
     11                parent::setUp();
     12                bp_update_option( 'bp_allow_hierarchical_groups', 1 );
     13                bp_update_option( 'bp_restrict_group_creation', 0 );
     14        }
     15
     16        public function test_update_orphaned_groups_on_group_delete_top_level() {
     17                $g1 = $this->factory->group->create();
     18                $g2 = $this->factory->group->create( array(
     19                        'parent_id' => $g1,
     20                ) );
     21
     22                groups_delete_group( $g1 );
     23
     24                $child = groups_get_group( array( 'group_id' => $g2 ) );
     25                $this->assertEquals( 0, $child->parent_id );
     26        }
     27
     28        public function test_update_orphaned_groups_on_group_delete_two_levels() {
     29                $g1 = $this->factory->group->create();
     30                $g2 = $this->factory->group->create( array(
     31                        'parent_id' => $g1,
     32                ) );
     33                $g3 = $this->factory->group->create( array(
     34                        'parent_id' => $g2,
     35                ) );
     36
     37                groups_delete_group( $g2 );
     38
     39                $child = groups_get_group( array( 'group_id' => $g3 ) );
     40                $this->assertEquals( $g1, $child->parent_id );
     41        }
     42
     43        public function test_bp_groups_get_child_groups_no_user_scope() {
     44                $g1 = $this->factory->group->create();
     45                $g2 = $this->factory->group->create( array(
     46                        'parent_id' => $g1,
     47                ) );
     48                $g3 = $this->factory->group->create( array(
     49                        'parent_id' => $g1,
     50                        'status'    => 'hidden',
     51                ) );
     52
     53                $children = bp_groups_get_child_groups( $g1 );
     54                $found = wp_list_pluck( $children, 'id' );
     55
     56                $this->assertEqualSets( array( $g2, $g3), $found );
     57        }
     58
     59        public function test_bp_groups_get_child_groups_user_scope_logged_out() {
     60                $g1 = $this->factory->group->create();
     61                $g2 = $this->factory->group->create( array(
     62                        'parent_id' => $g1,
     63                ) );
     64                $g3 = $this->factory->group->create( array(
     65                        'parent_id' => $g1,
     66                        'status'    => 'hidden',
     67                ) );
     68
     69                $children = bp_groups_get_child_groups( $g1, 0 );
     70                $found = wp_list_pluck( $children, 'id' );
     71
     72                $this->assertEqualSets( array( $g2 ), $found );
     73        }
     74
     75        public function test_bp_groups_get_child_groups_user_scope_not_group_member() {
     76                $g1 = $this->factory->group->create();
     77                $g2 = $this->factory->group->create( array(
     78                        'parent_id' => $g1,
     79                ) );
     80                $g3 = $this->factory->group->create( array(
     81                        'parent_id' => $g1,
     82                        'status'    => 'hidden',
     83                ) );
     84                $u1 = $this->factory->user->create();
     85
     86                $children = bp_groups_get_child_groups( $g1, $u1 );
     87                $found = wp_list_pluck( $children, 'id' );
     88
     89                $this->assertEqualSets( array( $g2 ), $found );
     90        }
     91
     92        public function test_bp_groups_get_child_groups_user_scope_group_member() {
     93                $u1 = $this->factory->user->create();
     94                $g1 = $this->factory->group->create();
     95                $g2 = $this->factory->group->create( array(
     96                        'parent_id' => $g1,
     97                ) );
     98                $g3 = $this->factory->group->create( array(
     99                        'parent_id'  => $g1,
     100                        'status'     => 'hidden',
     101                        'creator_id' => $u1,
     102                ) );
     103
     104                $children = bp_groups_get_child_groups( $g1, $u1 );
     105                $found = wp_list_pluck( $children, 'id' );
     106
     107                $this->assertEqualSets( array( $g2, $g3 ), $found );
     108        }
     109
     110        public function test_bp_groups_get_child_groups_user_scope_site_admin() {
     111                $u1 = $this->factory->user->create( array( 'role' => 'administrator' ) );
     112                $g1 = $this->factory->group->create();
     113                $g2 = $this->factory->group->create( array(
     114                        'parent_id' => $g1,
     115                ) );
     116                $g3 = $this->factory->group->create( array(
     117                        'parent_id'  => $g1,
     118                        'status'     => 'hidden',
     119                ) );
     120
     121                $children = bp_groups_get_child_groups( $g1, $u1 );
     122                $found = wp_list_pluck( $children, 'id' );
     123
     124                $this->assertEqualSets( array( $g2, $g3 ), $found );
     125        }
     126
     127        public function test_bp_groups_get_descendent_groups_no_user_scope() {
     128                $g1 = $this->factory->group->create();
     129                $g2 = $this->factory->group->create( array(
     130                        'parent_id' => $g1,
     131                ) );
     132                $g3 = $this->factory->group->create( array(
     133                        'parent_id' => $g1,
     134                        'status'    => 'hidden',
     135                ) );
     136                $g4 = $this->factory->group->create( array(
     137                        'parent_id' => $g2,
     138                ) );
     139                $g5 = $this->factory->group->create();
     140
     141                $children = bp_groups_get_descendent_groups( $g1 );
     142                $found = wp_list_pluck( $children, 'id' );
     143
     144                $this->assertEqualSets( array( $g2, $g3, $g4 ), $found );
     145        }
     146
     147        public function test_bp_groups_get_descendent_groups_user_scope_logged_out() {
     148                $g1 = $this->factory->group->create();
     149                $g2 = $this->factory->group->create( array(
     150                        'parent_id' => $g1,
     151                ) );
     152                $g3 = $this->factory->group->create( array(
     153                        'parent_id' => $g1,
     154                        'status'    => 'hidden',
     155                ) );
     156                $g4 = $this->factory->group->create( array(
     157                        'parent_id' => $g2,
     158                ) );
     159                $g5 = $this->factory->group->create();
     160
     161                $children = bp_groups_get_descendent_groups( $g1, 0 );
     162                $found = wp_list_pluck( $children, 'id' );
     163
     164                $this->assertEqualSets( array( $g2, $g4 ), $found );
     165        }
     166
     167        public function test_bp_groups_get_descendent_groups_user_scope_not_group_member() {
     168                $g1 = $this->factory->group->create();
     169                $g2 = $this->factory->group->create( array(
     170                        'parent_id' => $g1,
     171                ) );
     172                $g3 = $this->factory->group->create( array(
     173                        'parent_id' => $g1,
     174                        'status'    => 'hidden',
     175                ) );
     176                $g4 = $this->factory->group->create( array(
     177                        'parent_id' => $g2,
     178                ) );
     179                $g5 = $this->factory->group->create();
     180                $u1 = $this->factory->user->create();
     181
     182                $children = bp_groups_get_descendent_groups( $g1, $u1 );
     183                $found = wp_list_pluck( $children, 'id' );
     184
     185                $this->assertEqualSets( array( $g2, $g4 ), $found );
     186        }
     187
     188        public function test_bp_groups_get_descendent_groups_user_scope_group_member() {
     189                $u1 = $this->factory->user->create();
     190                $g1 = $this->factory->group->create();
     191                $g2 = $this->factory->group->create( array(
     192                        'parent_id' => $g1,
     193                ) );
     194                $g3 = $this->factory->group->create( array(
     195                        'parent_id'  => $g1,
     196                        'status'     => 'hidden',
     197                        'creator_id' => $u1,
     198                ) );
     199                $g4 = $this->factory->group->create( array(
     200                        'parent_id' => $g2,
     201                ) );
     202                $g5 = $this->factory->group->create();
     203
     204                $children = bp_groups_get_descendent_groups( $g1, $u1 );
     205                $found = wp_list_pluck( $children, 'id' );
     206
     207                $this->assertEqualSets( array( $g2, $g3, $g4 ), $found );
     208        }
     209
     210        public function test_bp_groups_get_descendent_groups_user_scope_site_admin() {
     211                $u1 = $this->factory->user->create( array( 'role' => 'administrator' ) );
     212                $this->grant_super_admin( $u1 );
     213                $g1 = $this->factory->group->create();
     214                $g2 = $this->factory->group->create( array(
     215                        'parent_id' => $g1,
     216                ) );
     217                $g3 = $this->factory->group->create( array(
     218                        'parent_id'  => $g1,
     219                        'status'     => 'hidden',
     220                ) );
     221                $g4 = $this->factory->group->create( array(
     222                        'parent_id' => $g2,
     223                ) );
     224                $g5 = $this->factory->group->create();
     225
     226                $children = bp_groups_get_descendent_groups( $g1, $u1 );
     227                $found = wp_list_pluck( $children, 'id' );
     228
     229                $this->assertEqualSets( array( $g2, $g3, $g4 ), $found );
     230        }
     231
     232        public function test_bp_groups_get_ancestor_group_ids_no_user_scope() {
     233                $g1 = $this->factory->group->create();
     234                $g2 = $this->factory->group->create( array(
     235                        'parent_id' => $g1,
     236                ) );
     237                $g3 = $this->factory->group->create( array(
     238                        'parent_id' => $g1,
     239                        'status'    => 'hidden',
     240                ) );
     241                $g4 = $this->factory->group->create( array(
     242                        'parent_id' => $g2,
     243                ) );
     244                $g5 = $this->factory->group->create();
     245
     246                $children = bp_groups_get_ancestor_group_ids( $g4 );
     247
     248                $this->assertEqualSets( array( $g1, $g2 ), $children );
     249        }
     250
     251        public function test_bp_groups_get_ancestor_group_ids_user_scope_logged_out() {
     252                $g1 = $this->factory->group->create();
     253                $g2 = $this->factory->group->create( array(
     254                        'parent_id' => $g1,
     255                ) );
     256                $g3 = $this->factory->group->create( array(
     257                        'parent_id' => $g1,
     258                        'status'    => 'hidden',
     259                ) );
     260                $g4 = $this->factory->group->create( array(
     261                        'parent_id' => $g2,
     262                ) );
     263                $g5 = $this->factory->group->create();
     264
     265                $children = bp_groups_get_ancestor_group_ids( $g4, 0 );
     266
     267                $this->assertEqualSets( array( $g1, $g2 ), $children );
     268        }
     269
     270        public function test_bp_groups_get_ancestor_group_ids_user_scope_logged_out_w_hidden() {
     271                $g1 = $this->factory->group->create();
     272                $g2 = $this->factory->group->create( array(
     273                        'parent_id' => $g1,
     274                ) );
     275                $g3 = $this->factory->group->create( array(
     276                        'parent_id' => $g1,
     277                        'status'    => 'hidden',
     278                ) );
     279                $g4 = $this->factory->group->create( array(
     280                        'parent_id' => $g3,
     281                ) );
     282                $g5 = $this->factory->group->create();
     283
     284                $children = bp_groups_get_ancestor_group_ids( $g4, 0 );
     285
     286                $this->assertEqualSets( array(), $children );
     287        }
     288
     289        public function test_bp_groups_get_ancestor_group_ids_user_scope_not_group_member() {
     290                $g1 = $this->factory->group->create( array(
     291                        'status' => 'hidden',
     292                ) );
     293                $g2 = $this->factory->group->create( array(
     294                        'parent_id' => $g1,
     295                ) );
     296                $g3 = $this->factory->group->create( array(
     297                        'parent_id' => $g1,
     298                ) );
     299                $g4 = $this->factory->group->create( array(
     300                        'parent_id' => $g3,
     301                ) );
     302                $g5 = $this->factory->group->create();
     303                $u1 = $this->factory->user->create();
     304
     305                $children = bp_groups_get_ancestor_group_ids( $g4, $u1 );
     306
     307                $this->assertEqualSets( array( $g3 ), $children );
     308        }
     309
     310        public function test_bp_groups_get_ancestor_group_ids_user_scope_group_member() {
     311                $u1 = $this->factory->user->create();
     312                $g1 = $this->factory->group->create();
     313                $g2 = $this->factory->group->create( array(
     314                        'parent_id' => $g1,
     315                ) );
     316                $g3 = $this->factory->group->create( array(
     317                        'parent_id'  => $g1,
     318                        'status'     => 'hidden',
     319                        'creator_id' => $u1,
     320                ) );
     321                $g4 = $this->factory->group->create( array(
     322                        'parent_id' => $g3,
     323                ) );
     324                $g5 = $this->factory->group->create();
     325
     326                $children = bp_groups_get_ancestor_group_ids( $g4, $u1 );
     327
     328                $this->assertEqualSets( array( $g1, $g3 ), $children );
     329        }
     330
     331        public function test_bp_groups_get_ancestor_group_ids_user_scope_site_admin() {
     332                $u1 = $this->factory->user->create( array( 'role' => 'administrator' ) );
     333                $this->grant_super_admin( $u1 );
     334                $g1 = $this->factory->group->create();
     335                $g2 = $this->factory->group->create( array(
     336                        'parent_id' => $g1,
     337                ) );
     338                $g3 = $this->factory->group->create( array(
     339                        'parent_id'  => $g1,
     340                        'status'     => 'hidden',
     341                ) );
     342                $g4 = $this->factory->group->create( array(
     343                        'parent_id' => $g3,
     344                ) );
     345                $g5 = $this->factory->group->create();
     346
     347                $children = bp_groups_get_ancestor_group_ids( $g4, $u1 );
     348
     349                $this->assertEqualSets( array( $g1, $g3 ), $children );
     350        }
     351
     352        public function test_bp_groups_get_possible_parent_groups_user_is_nonmember_member_allowed() {
     353                $u1 = $this->factory->user->create();
     354                $g1 = $this->factory->group->create();
     355                $g2 = $this->factory->group->create( array(
     356                        'parent_id' => $g1,
     357                ) );
     358                $g3 = $this->factory->group->create( array(
     359                        'parent_id'  => $g1,
     360                ) );
     361                $g4 = $this->factory->group->create( array(
     362                        'parent_id' => $g3,
     363                ) );
     364                $g5 = $this->factory->group->create();
     365                $g6 = $this->factory->group->create();
     366
     367                // Members can create subgroups.
     368                groups_update_groupmeta( $g6, 'allowed_subgroup_creators', 'member' );
     369
     370                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     371                $found = wp_list_pluck( $groups, 'id' );
     372
     373                $this->assertEqualSets( array(), $found );
     374        }
     375
     376        public function test_bp_groups_get_possible_parent_groups_user_is_member_member_allowed() {
     377                $u1 = $this->factory->user->create();
     378                $g1 = $this->factory->group->create();
     379                $g2 = $this->factory->group->create( array(
     380                        'parent_id' => $g1,
     381                ) );
     382                $g3 = $this->factory->group->create( array(
     383                        'parent_id'  => $g1,
     384                ) );
     385                $g4 = $this->factory->group->create( array(
     386                        'parent_id' => $g3,
     387                ) );
     388                $g5 = $this->factory->group->create();
     389                $g6 = $this->factory->group->create();
     390
     391                // Make $u1 a member.
     392                $this->add_user_to_group( $u1, $g6 );
     393
     394                // Members can create subgroups.
     395                groups_add_groupmeta( $g6, 'allowed_subgroup_creators', 'member' );
     396
     397                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     398                $found = wp_list_pluck( $groups, 'id' );
     399
     400                $this->assertEqualSets( array( $g6 ), $found );
     401        }
     402
     403        public function test_bp_groups_get_possible_parent_groups_user_is_member_mod_allowed() {
     404                $u1 = $this->factory->user->create();
     405                $g1 = $this->factory->group->create();
     406                $g2 = $this->factory->group->create( array(
     407                        'parent_id' => $g1,
     408                ) );
     409                $g3 = $this->factory->group->create( array(
     410                        'parent_id'  => $g1,
     411                ) );
     412                $g4 = $this->factory->group->create( array(
     413                        'parent_id' => $g3,
     414                ) );
     415                $g5 = $this->factory->group->create();
     416                $g6 = $this->factory->group->create();
     417
     418                // Make $u1 a member.
     419                $this->add_user_to_group( $u1, $g6 );
     420
     421                // Members can create subgroups.
     422                groups_update_groupmeta( $g6, 'allowed_subgroup_creators', 'mod' );
     423
     424                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     425                $found = wp_list_pluck( $groups, 'id' );
     426
     427                $this->assertEqualSets( array(), $found );
     428        }
     429
     430        public function test_bp_groups_get_possible_parent_groups_user_is_mod_mod_allowed() {
     431                $u1 = $this->factory->user->create();
     432                $g1 = $this->factory->group->create();
     433                $g2 = $this->factory->group->create( array(
     434                        'parent_id' => $g1,
     435                ) );
     436                $g3 = $this->factory->group->create( array(
     437                        'parent_id'  => $g1,
     438                ) );
     439                $g4 = $this->factory->group->create( array(
     440                        'parent_id' => $g3,
     441                ) );
     442                $g5 = $this->factory->group->create();
     443                $g6 = $this->factory->group->create();
     444
     445                // Make $u1 a member, then promote.
     446                $this->add_user_to_group( $u1, $g6 );
     447                $m1 = new BP_Groups_Member( $u1, $g6 );
     448                $m1->promote( 'mod' );
     449
     450                // Members can create subgroups.
     451                groups_update_groupmeta( $g6, 'allowed_subgroup_creators', 'mod' );
     452
     453                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     454                $found = wp_list_pluck( $groups, 'id' );
     455
     456                $this->assertEqualSets( array( $g6 ), $found );
     457        }
     458
     459        public function test_bp_groups_get_possible_parent_groups_user_is_mod_admin_allowed() {
     460                $u1 = $this->factory->user->create();
     461                $g1 = $this->factory->group->create();
     462                $g2 = $this->factory->group->create( array(
     463                        'parent_id' => $g1,
     464                ) );
     465                $g3 = $this->factory->group->create( array(
     466                        'parent_id'  => $g1,
     467                ) );
     468                $g4 = $this->factory->group->create( array(
     469                        'parent_id' => $g3,
     470                ) );
     471                $g5 = $this->factory->group->create();
     472                $g6 = $this->factory->group->create();
     473
     474                // Make $u1 a member, then promote.
     475                $this->add_user_to_group( $u1, $g6 );
     476                $m1 = new BP_Groups_Member( $u1, $g6 );
     477                $m1->promote( 'mod' );
     478
     479                // Members can create subgroups.
     480                groups_update_groupmeta( $g6, 'allowed_subgroup_creators', 'admin' );
     481
     482                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     483                $found = wp_list_pluck( $groups, 'id' );
     484
     485                $this->assertEqualSets( array(), $found );
     486        }
     487
     488        public function test_bp_groups_get_possible_parent_groups_user_is_admin_admin_allowed() {
     489                $u1 = $this->factory->user->create();
     490                $g1 = $this->factory->group->create();
     491                $g2 = $this->factory->group->create( array(
     492                        'parent_id' => $g1,
     493                ) );
     494                $g3 = $this->factory->group->create( array(
     495                        'parent_id'  => $g1,
     496                ) );
     497                $g4 = $this->factory->group->create( array(
     498                        'parent_id' => $g3,
     499                ) );
     500                $g5 = $this->factory->group->create();
     501                $g6 = $this->factory->group->create();
     502
     503                // Make $u1 a member, then promote.
     504                $this->add_user_to_group( $u1, $g6 );
     505                $m1 = new BP_Groups_Member( $u1, $g6 );
     506                $m1->promote( 'admin' );
     507
     508                // Members can create subgroups.
     509                groups_update_groupmeta( $g6, 'allowed_subgroup_creators', 'admin' );
     510
     511                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     512                $found = wp_list_pluck( $groups, 'id' );
     513
     514                $this->assertEqualSets( array( $g6 ), $found );
     515        }
     516
     517        public function test_bp_groups_get_possible_parent_groups_user_is_admin_noone_allowed() {
     518                $u1 = $this->factory->user->create();
     519                $g1 = $this->factory->group->create();
     520                $g2 = $this->factory->group->create( array(
     521                        'parent_id' => $g1,
     522                ) );
     523                $g3 = $this->factory->group->create( array(
     524                        'parent_id'  => $g1,
     525                ) );
     526                $g4 = $this->factory->group->create( array(
     527                        'parent_id' => $g3,
     528                ) );
     529                $g5 = $this->factory->group->create();
     530                $g6 = $this->factory->group->create();
     531
     532                // Make $u1 a member, then promote.
     533                $this->add_user_to_group( $u1, $g6 );
     534                $m1 = new BP_Groups_Member( $u1, $g6 );
     535                $m1->promote( 'admin' );
     536
     537                // Members can create subgroups.
     538                groups_update_groupmeta( $g6, 'allowed_subgroup_creators', 'noone' );
     539
     540                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     541                $found = wp_list_pluck( $groups, 'id' );
     542
     543                $this->assertEqualSets( array(), $found );
     544        }
     545
     546        public function test_bp_groups_get_possible_parent_groups_user_scope_site_admin() {
     547                $u1 = $this->factory->user->create( array( 'role' => 'administrator' ) );
     548                $this->grant_super_admin( $u1 );
     549                $g1 = $this->factory->group->create();
     550                $g2 = $this->factory->group->create( array(
     551                        'parent_id' => $g1,
     552                ) );
     553                $g3 = $this->factory->group->create( array(
     554                        'parent_id'  => $g1,
     555                ) );
     556                $g4 = $this->factory->group->create( array(
     557                        'parent_id' => $g3,
     558                ) );
     559                $g5 = $this->factory->group->create();
     560                $g6 = $this->factory->group->create();
     561
     562                $groups = bp_groups_get_possible_parent_groups( $g1, $u1 );
     563                $found = wp_list_pluck( $groups, 'id' );
     564
     565                $this->assertEqualSets( array( $g5, $g6 ), $found );
     566        }
     567
     568}
     569 No newline at end of file
  • tests/phpunit/testcases/groups/functions.php

    diff --git tests/phpunit/testcases/groups/functions.php tests/phpunit/testcases/groups/functions.php
    index 22f4c53..4af9454 100644
    Bar!'; 
    664664                groups_accept_invite( $u2, $g );
    665665                $this->assertEquals( 0, groups_get_invite_count_for_user( $u2 ) );
    666666        }
     667
    667668}