You are here

og_subgroups.module in Subgroups for Organic groups 5.3

Maintains a hierarchy of group/subgroup relationships.

File

og_subgroups.module
View source
<?php

/**
 * @file
 * Maintains a hierarchy of group/subgroup relationships.
 */
function og_subgroups_menu($may_cache) {
  $items = array();
  if (!$may_cache) {
    $arg0 = arg(0);
    $arg1 = arg(1);
    if ($arg0 == 'node' && is_numeric($arg1)) {
      $node = node_load($arg1);
      if (og_is_group_type($node->type)) {

        //Begin tabs displayed on node view.
        if (user_access('access og_subgroups node view tabs')) {
          $items[] = array(
            'path' => 'node/' . $arg1 . '/view/nodes',
            'title' => t('Overview'),
            'access' => node_access('view', $node),
            'type' => MENU_DEFAULT_LOCAL_TASK,
            'weight' => 0,
          );
          $items[] = array(
            'path' => 'node/' . $arg1 . '/view/tree',
            'title' => t('Tree'),
            'callback' => 'og_subgroups_page',
            'callback arguments' => $arg1,
            'access' => node_access('view', $node),
            'type' => MENU_LOCAL_TASK,
            'weight' => 5,
          );
          if (_og_subgroups_can_view_members($arg1)) {
            $items[] = array(
              'path' => 'node/' . $arg1 . '/view/members',
              'title' => t('Members'),
              'callback' => 'og_subgroups_members_page',
              'callback arguments' => $arg1,
              'access' => node_access('view', $node),
              'type' => MENU_LOCAL_TASK,
              'weight' => 7,
            );
          }
        }

        //End of tabs displayed on node view.
        $items[] = array(
          'path' => 'node/' . $arg1 . '/edit/group',
          'title' => t('Group'),
          'access' => node_access('update', $node),
          'type' => MENU_DEFAULT_LOCAL_TASK,
        );
        $items[] = array(
          'path' => 'node/' . $arg1 . '/edit/children',
          'title' => t('SubGroups'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array(
            'og_subgroups_edit_children_page',
            $arg1,
          ),
          'access' => user_access('edit subgroups hierarchy'),
          'type' => MENU_LOCAL_TASK,
          'weight' => 3,
        );
        $items[] = array(
          'path' => 'node/' . $arg1 . '/edit/members',
          'title' => t('Members'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array(
            'og_subgroups_edit_members_page',
            $arg1,
          ),
          'access' => user_access('edit subgroups members'),
          'type' => MENU_LOCAL_TASK,
          'weight' => 5,
        );
      }
    }
  }
  else {
    $items[] = array(
      'path' => 'admin/og/subgroups',
      'title' => t('OG Subgroups Settings'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'og_subgroups_settings',
      ),
      'access' => user_access('administer og_subgroups'),
      'weight' => 1,
    );
  }
  return $items;
}

/**
 * Implementation of hook_perm().
 */
function og_subgroups_perm() {
  return array(
    'edit subgroups hierarchy',
    'edit subgroups members',
    'administer og_subgroups',
    'access og_subgroups node view tabs',
  );
}
function og_subgroups_set_breadcrumb($addchild = false) {
  $gnode = og_get_group_context();
  $parent = _og_subgroups_get_parent($gnode->nid);
  if ($parent) {
    $pnode = node_load($parent);
    $bc[] = l(t('Home'), NULL);
    $bc[] = l(t('Groups'), 'og');
    $bc[] = l($pnode->title, "node/{$pnode->nid}");
    if ($addchild) {
      $bc[] = l($gnode->title, "node/{$gnode->nid}");
    }
    drupal_set_breadcrumb($bc);
  }
}
function og_subgroups_settings() {
  $form['og_subgroups_settings']['og_subgroups_parent_children_types'] = array(
    '#type' => 'fieldset',
    '#title' => t('Parent-Child Group Relationships'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['og_subgroups_settings']['og_subgroups_prop_type'] = array(
    '#type' => 'radios',
    '#title' => t('Post Propagation Direction'),
    '#default_value' => variable_get('og_subgroups_prop_type', 'none'),
    '#options' => array(
      'parents' => t('Up the tree'),
      'children' => t('Down the tree'),
      'none' => t('No propagation'),
    ),
    '#multiple' => FALSE,
    '#description' => t("When a post is created inside an OG, should it propagate to that group's parents, children, or not at all?"),
  );
  $og_types = variable_get('og_node_types', array());
  foreach ($og_types as $child_type) {
    $form['og_subgroups_settings']['og_subgroups_parent_children_types']['og_subgroups_child_' . $child_type] = array(
      '#type' => 'fieldset',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#title' => ucfirst($child_type),
    );
    $form['og_subgroups_settings']['og_subgroups_parent_children_types']['og_subgroups_child_' . $child_type]['og_subgroups_form_settings_' . $child_type] = array(
      '#type' => 'fieldset',
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
      '#title' => t('Form Settings'),
    );
    $form['og_subgroups_settings']['og_subgroups_parent_children_types']['og_subgroups_child_' . $child_type]['og_subgroups_form_settings_' . $child_type]['og_subgroups_' . $child_type . '_set_parents'] = array(
      '#type' => 'checkbox',
      '#title' => t("Provide a 'set parents' form element"),
      '#default_value' => variable_get('og_subgroups_' . $child_type . '_set_parents', 1),
    );
    $form['og_subgroups_settings']['og_subgroups_parent_children_types']['og_subgroups_child_' . $child_type]['og_subgroups_form_settings_' . $child_type]['og_subgroups_' . $child_type . '_set_members'] = array(
      '#type' => 'checkbox',
      '#title' => t("Provide a 'set members' form element"),
      '#default_value' => variable_get('og_subgroups_' . $child_type . '_set_members', 1),
    );
    $other_og_types = $og_types;
    $form['og_subgroups_settings']['og_subgroups_parent_children_types']['og_subgroups_child_' . $child_type]['og_subgroups_' . $child_type . '_parents'] = array(
      '#type' => 'select',
      '#multiple' => TRUE,
      '#options' => $other_og_types,
      '#validate' => array(
        'og_subgroups_parent_types_validate' => array(),
      ),
      '#title' => t('Parent Types'),
      '#default_value' => variable_get('og_subgroups_' . $child_type . '_parents', $og_other_types),
    );
  }
  return system_settings_form($form);
}
function og_subgroups_parent_types_validate($element) {
  $value = $element['#value'];
  if (array_key_exists('none', $value)) {
    unset($value['none']);
    if (count($value) > 0) {
      form_error($element, t("Please choose either 'none' or one or more node types."));
    }
  }
}
function og_subgroups_nodeapi($node, $op, $teaser = NULL, $page = NULL) {
  switch ($op) {
    case 'load':
      if ($grps = og_subgroups_get_node_groups($node)) {

        // TODO: Refactor so we don't need 2 arrays.
        $node->og_groups = array_keys($grps);
        $node->og_groups_names = array_values($grps);
      }
      break;
    case 'view':
      og_subgroups_set_breadcrumb(!og_is_group_type($node->type));
      break;
    case 'insert':
      if (og_is_group_type($node->type) && is_array($node->members)) {
        og_subgroups_save_members($node->nid, $node->members);
      }
      og_subgroups_save_family($node, variable_get('og_subgroups_prop_type', 'none'));
      break;
    case 'update':
      if (og_is_group_type($node->type) && is_array($node->members)) {
        og_subgroups_save_members($node->nid, $node->members);
      }
      og_subgroups_save_family($node, variable_get('og_subgroups_prop_type', 'none'));
      break;
  }
}

// returns all the group affiliations for a given subgroup.
function og_subgroups_get_node_groups($node) {
  $groups = array();
  $parent_types = variable_get('og_subgroups_' . $node->type . '_parents', array());
  if (og_is_group_type($node->type && !in_array('none', $parent_types))) {
    $result = og_get_node_groups_result($node->nid);
    while ($row = db_fetch_object($result)) {
      $groups[$row->group_nid] = $row->title;
    }
    return $groups;
  }
}

/**
 * Implementation of hook_form_alter().
 */
function og_subgroups_form_alter($form_id, &$form) {
  if (strpos($form_id, 'node_form')) {
    $node = $form['#node'];
    if (og_is_group_type($node->type)) {
      if (user_access('edit subgroups hierarchy') && variable_get('og_subgroups_' . $node->type . '_set_parents', 1)) {
        $selected = array();
        if (isset($node->nid)) {
          $selected = og_subgroups_get_parents($node->nid);
        }
        og_subgroups_form_add_audience($form_id, $form);
      }
      if (user_access('edit subgroups members') && variable_get('og_subgroups_' . $node->type . '_set_members', 1)) {
        $selected = array();
        if (isset($node->nid)) {
          $selected = og_subgroups_get_users('names', $node->nid, 0);
        }
        $form['members'] = og_subgroups_members_select($selected);
      }
    }
  }
}
function og_subgroups_get_sql_args($type) {
  if (empty($type)) {
    $types = variable_get('og_node_types', array(
      'og',
    ));
  }
  else {
    $types = variable_get('og_subgroups_' . $type . '_parents', array());
  }
  $in = implode(', ', array_fill(0, count($types), "'%s'"));
  return array(
    $types,
    $in,
  );
}
function og_subgroups_selective_groups_options($type) {

  //Note that these two things are possible:

  //1) Type is not set, in which case we return a non-selective list of node types.

  //2) Type is set but this type has no parent types available
  if (empty($type) && !in_array($type, variable_get('og_node_types', array(
    'og',
  )))) {

    //type is not set or node is not an og_type
    return og_all_groups_options();
  }
  else {
    $types = variable_get('og_subgroups_' . $type . '_parents', array());
  }
  if (!empty($types) && count($types) > 0) {

    //Does $types have any contents?
    list($types, $in) = og_subgroups_get_sql_args($type);
    $sql = "SELECT n.title, n.nid FROM {node} n WHERE n.type IN ({$in}) AND n.status = 1 ORDER BY n.title ASC";
    $result = db_query($sql, $types);
    while ($row = db_fetch_object($result)) {
      $options[$row->nid] = $row->title;
    }
    return $options ? $options : array();
  }
  else {
    return array();

    //If not, return an empty array.
  }
}

/**
 * A modified version of og_form_add_og_audience that takes into account appropriate parent types of OG homepage nodes.
 */
function og_subgroups_form_add_audience($form_id, &$form) {
  global $user;
  $node = $form['#node'];
  $prop_type = variable_get('og_subgroups_prop_type', 'none');
  $filtered_options = og_subgroups_selective_groups_options($node->type);
  $og_node_types = variable_get('og_node_types', array(
    'og',
  ));
  $parent_node_types = variable_get('og_subgroups_' . $node->type . '_parents', array(
    $og_node_types,
  ));

  //Avoided nesting another variable_get here.

  // determine the selected groups if fapi doesn't tell us.
  if ($_SERVER["REQUEST_METHOD"] == 'GET') {
    $gids = $_GET['gids'];
  }
  elseif ($_POST['og_groups_hidden']) {
    $gids = unserialize($_POST['og_groups_hidden']);
  }
  $required = variable_get('og_audience_required', 0) && !user_access('administer nodes');

  // Determine the list of groups that are shown.
  // Start by collecting all groups that the user is a member of.
  $subs = og_get_subscriptions($user->uid);
  $options = array();
  foreach ($subs as $key => $val) {
    if (isset($parent_node_types[$val['type']])) {
      $options[$key] = $val['title'];
    }
  }
  if (user_access('administer nodes')) {

    // Node admins see all of groups.
    $other = array_diff_assoc($filtered_options, $options);

    // Use an optgroup if admin is not a member of all groups.
    if ($other) {
      $options = array(
        t('My subscribed groups') => $options,
        t('Other groups') => $other,
      );
      $is_optgroup = TRUE;
    }
    else {
      $options = $all;
    }
  }
  else {

    // Classify those groups which the node already has but the author does not.
    if (function_exists('og_node_groups_distinguish')) {
      $current_groups = og_node_groups_distinguish($node->og_groups, $node->og_groups_names);
    }

    // Put inaccessible groups in the $form so that they can persist. See og_submit_group()
    $form['og_invisible']['og_groups_inaccessible'] = array(
      '#type' => 'value',
      '#value' => $current_groups['inaccessible'],
    );

    // Add the accessible groups that they node already belongs to.
    if ($current_groups['accessible']) {

      // Use an optgroup to distinguish between my subscriptions and additional groups in the Audience select.
      // There is code below which assumes that $options does not have optgroups but that code is within a $simple check
      // So we are OK as long as $simple does not apply to node edits.
      // NOTE: If you form_alter the audience element, beware that it can sometimes be an optgroup.
      foreach ($current_groups['accessible'] as $key => $val) {
        $other[$key] = $val['title'];
      }
      $options = array(
        t('My subscribed groups') => $options,
        t('Other groups') => $other,
      );
      $is_optgroup = TRUE;
    }
    else {
      $is_optgroup = FALSE;
    }
  }

  // show read only item if we are non-admin, and in simple mode (i.e. non-checkboxes) and at least one group is in querystring
  $simple = !user_access('administer organic groups') && !variable_get('og_audience_checkboxes', TRUE) && count($gids);

  // determine value of audience multi-select
  if (count($options) == 1 && $required) {
    $gids = array_keys($options);
    $gid = $gids[0];
    $groups = array(
      $gid,
    );

    // also show read only mode if user has 1 option and we are in required mode
    $simple = TRUE;
  }
  elseif ($gids) {

    // populate field from the querystring if sent
    $groups = $gids;
    if (!user_access('administer nodes') && $simple) {

      // filter out any groups where author is not a member. we cannot rely on fapi to do this when in simple mode.
      $groups = array_intersect($gids, array_keys($options));
    }
  }
  elseif ($node->nid || $node->og_groups) {
    $groups = $node->og_groups;
  }
  else {
    $groups = array();
  }

  // don't bother with visibility if access control is disabled. all is public.
  if (variable_get('og_enabled', FALSE)) {

    // get the visibility for normal users
    $vis = variable_get('og_visibility', 0);

    // override visibility for og admins and when author only has 1 group
    if (user_access('administer organic groups') && $vis < 2) {
      $vis = $vis == OG_VISIBLE_GROUPONLY ? OG_VISIBLE_CHOOSE_PRIVATE : OG_VISIBLE_CHOOSE_PUBLIC;
    }
    elseif (!count($options)) {

      // don't show checkbox if no subscriptions. must be public.
      $vis = OG_VISIBLE_BOTH;
    }

    // If the post is to a private group, visibility must default to one of the private options.
    // Try not to show checkbox if admin likes to reduce decisions for node authors.
    $selected_groups = isset($form['#post']['og_groups']) ? array_filter($form['#post']['og_groups']) : $groups;
    if (count($selected_groups)) {
      foreach ($selected_groups as $gid) {
        $group_node = new stdClass();
        $group_node->nid = $gid;
        og_load_group($group_node);
        if ($group_node->og_private) {
          $vis = variable_get('og_visibility', 0) == OG_VISIBLE_BOTH ? OG_VISIBLE_GROUPONLY : OG_VISIBLE_CHOOSE_PRIVATE;
          break;
        }
      }
    }
    switch ($vis) {
      case OG_VISIBLE_BOTH:
        $form['og_nodeapi']['og_public'] = array(
          '#type' => 'value',
          '#value' => 1,
        );
        break;
      case OG_VISIBLE_GROUPONLY:
        $form['og_nodeapi']['og_public'] = array(
          '#type' => 'value',
          '#value' => 0,
        );
        break;

      //user decides how public the post is.
      case OG_VISIBLE_CHOOSE_PUBLIC:
        $form['og_nodeapi']['visible']['og_public'] = array(
          '#type' => 'checkbox',
          '#title' => t('Public'),
          '#default_value' => $node->nid ? $node->og_public : 1,
          '#description' => t('Show this post to everyone, or only to subscribers of the groups checked above. Posts without any groups are always <em>Public</em>.'),
          '#weight' => 2,
        );
        break;
      case OG_VISIBLE_CHOOSE_PRIVATE:
        $form['og_nodeapi']['visible']['og_public'] = array(
          '#type' => 'checkbox',
          '#title' => t('Public'),
          '#default_value' => $node->nid ? $node->og_public : 0,
          '#description' => t('Show this post to everyone, or only to subscribers of the groups checked above. Posts without any groups are always <em>Public</em>.'),
          '#weight' => 2,
        );
        break;
    }
  }

  // Emit the audience form element.
  if ($simple) {

    // 'simple' mode. read only.
    if (count($groups)) {
      foreach ($groups as $gid) {
        $titles[] = $options[$gid];
        $item_value = implode(', ', $titles);
      }
      $form['og_nodeapi']['visible']['og_groups_visible'] = array(
        '#type' => 'item',
        '#title' => t('Audience'),
        '#value' => $item_value,
      );
      $assoc_groups = drupal_map_assoc($groups);

      // this hidden element persists audience values during a Preview cycle. avoids errors on Preview.
      $form['og_nodeapi']['invisible']['og_groups_hidden'] = array(
        '#type' => 'hidden',
        '#value' => serialize($assoc_groups),
      );

      // this 'value' element persists the audience value during submit process
      $form['og_nodeapi']['invisible']['og_groups'] = array(
        '#type' => 'value',
        '#value' => $assoc_groups,
      );
    }
  }
  elseif ($cnt = count($options, COUNT_RECURSIVE)) {
    drupal_add_js(drupal_get_path('module', 'og') . '/og.js');

    // show multi-select. if less than 20 choices, use checkboxes.
    $type = $cnt >= 20 || $is_optgroup ? 'select' : 'checkboxes';
    $form['og_nodeapi']['visible']['og_groups'] = array(
      '#type' => $type,
      '#title' => t('Audience'),
      '#attributes' => array(
        'class' => 'og-audience',
      ),
      '#options' => $options,
      '#required' => $required,
      '#description' => format_plural(count($options), 'Show this post in this group.', 'Show this post in these groups.'),
      '#default_value' => $groups,
      '#required' => $required,
      '#multiple' => TRUE,
    );
  }
  else {
    if ($required) {
      form_set_error('title', t('You must !join before posting a %type.', array(
        '!join' => l(t('join a group'), 'og'),
        '%type' => node_get_types('name', $node->type),
      )));
    }
  }
  if (count($form['og_nodeapi']['visible']) > 1) {
    $form['og_nodeapi']['#type'] = 'fieldset';
    $form['og_nodeapi']['#title'] = t('Groups');
    $form['og_nodeapi']['#collapsible'] = TRUE;
    $form['og_nodeapi']['#collapsed'] = $gids ? TRUE : FALSE;
  }
}

/**
 * Lists users of a site so members can be specified
 */

//function og_subgroups_members_select($selected = array()) {
function og_subgroups_members_select($selected = array()) {
  $options = og_subgroups_get_users('names');
  $selected = array_keys($selected);
  unset($options[0]);
  $type = count($options) >= 20 ? 'select' : 'checkboxes';
  return array(
    '#type' => $type,
    '#title' => t('Members'),
    '#default_value' => $selected,
    '#options' => $options,
    '#description' => t('Anyone who is an immediate member of the group.  Members of subgroups need not be checked.'),
    '#multiple' => 1,
    '#size' => $multiple ? min(9, count($options)) : 0,
    '#weight' => 0,
  );
}
function og_subgroups_parents_select($title, $name, $selected, $gid, $type, $exclude = array()) {
  $child_types = variable_get('og_subgroups_' . $type . '_parents', array());
  if (count($child_types) == 0) {
    return "";
  }
  $options = array();
  $query = "SELECT og.nid, n.title FROM {og} og INNER JOIN {node} n where og.nid=n.nid AND og.nid <> %d";
  $add = " and (";
  foreach ($child_types as $type) {
    $query .= $add . "n.type = '" . $type . "'";
    $add = " or ";
  }
  $query .= ")";
  $result = db_query($query, $gid);
  while ($row = db_fetch_object($result)) {
    if (!in_array($row->nid, $exclude)) {
      $options[$row->nid] = $row->title;
    }
  }
  $selected = array_keys($selected);
  if (count($options)) {
    $type = count($options) >= 20 ? 'select' : 'checkboxes';
    return array(
      '#type' => $type,
      '#title' => $title,
      '#default_value' => $selected,
      '#options' => $options,
      '#description' => '',
      '#multiple' => 1,
      '#size' => min(9, count($options)),
      '#weight' => -1,
    );
  }
  else {
    return "";
  }
}

/**
 * Returns the parent for a given groupnode id
 */
function _og_subgroups_get_parent($gid) {
  $sql = "SELECT oga.group_nid FROM {og_ancestry} oga where oga.nid = %d";
  return db_result(db_query($sql, $gid));
}
function _og_subgroups_can_view_members($gid) {
  global $user;
  if ($user->uid == 1) {
    return true;
  }
  return in_array($gid, array_keys($user->og_groups));
}

/**
 * Output a viewable page of group members
 */
function og_subgroups_members_page($gid) {
  global $user;

  // get the user object
  // Need to make sure a user can only see members if he is a member of the group
  if (!_og_subgroups_can_view_members($gid)) {
    drupal_access_denied();
    return;
  }
  $node = node_load($gid);
  drupal_set_title(check_plain($node->title));
  og_subgroups_set_breadcrumb(true);

  // Grab users and theme display
  $users = og_subgroups_get_users('links', $gid, 1);
  if (!count($users)) {
    $users = array(
      "(no members)",
    );
  }
  $output = theme('og_subgroups_members_list', array_unique($users));
  return $output;
}
function theme_og_subgroups_members_list($members = array()) {
  $output = implode($members, ', ');
  return $output;
}

/**
 * Retrieve an array of all users that are members of the group
 * If the recursive flag is true, then users of children are returned as well
 */
function og_subgroups_get_users($return_type = 'users', $gid = 0, $recursive = 0) {
  if ($gid > 0) {
    $result = db_query(og_list_users_sql(), $gid);
  }
  else {
    $result = db_query("SELECT u.uid, u.name, u.mail, u.picture FROM {users} u");
  }
  $users = array();
  while ($user = db_fetch_object($result)) {
    if ($return_type == 'links') {
      $users[$user->uid] = theme('username', $user);
    }
    if ($return_type == 'users') {
      $users[$user->uid] = $user;
    }
    if ($return_type == 'names') {
      $users[$user->uid] = $user->name;
    }
  }
  if ($recursive) {
    $children = og_subgroups_get_children($gid);
    foreach ($children as $cid => $cname) {

      // We'll have to merge ourselves due to desire to have unique keys
      // Original code just used array_merge but this leaves us with reindexed numeric keys
      // which will not work.  The uid keys MUST be preserved uniquely.
      $susers = og_subgroups_get_users($return_type, $cid, 1);
      foreach ($susers as $sid => $cname) {
        if (empty($users[$sid])) {
          $users[$sid] = $cname;
        }
      }
    }
  }
  return $users;
}

/**
 * Return an array of children groups of the group
 */
function og_subgroups_get_children($gid) {
  $result = db_query('SELECT oga.nid, n.title, n.type FROM {og_ancestry} oga INNER JOIN {node} n WHERE oga.group_nid=%d AND oga.nid=n.nid', $gid);
  $children = array();
  while ($row = db_fetch_object($result)) {
    if (og_is_group_type($row->type)) {
      $children[$row->nid] = $row->title;
    }
  }
  return $children;
}
function og_subgroups_page($gid) {
  $node = node_load($gid);
  drupal_set_title(check_plain($node->title));
  og_subgroups_set_breadcrumb(true);
  return og_subgroups_tree(array(
    $node->nid => $node->title,
  ));
}
function og_subgroups_tree($children, $depth = 0) {
  global $user;
  if ($depth == 0) {
    drupal_add_js(drupal_get_path('module', 'og_subgroups') . '/og_subgroups.js');
    $togo = "<ul class=collapsible>";
  }
  else {
    $togo = "<ul>";
  }
  foreach ($children as $cid => $cname) {
    $show_members = _og_subgroups_can_view_members($cid);
    if ($depth == 0) {
      $link = '<span><a href="#">' . $cname . '</a></span>';
      if ($show_members) {
        $link .= ' ' . l('[' . t('list all members') . ']', 'node/' . $cid . '/view/members');
      }
      $togo .= '<li>' . $link;
      $togo .= og_subgroups_tree(og_subgroups_get_children($cid), $depth + 1);
      if ($show_members) {
        $togo .= og_subgroups_make_list(og_subgroups_get_users('links', $cid, 0));
      }
      $togo .= '</li>';
    }
    else {
      $link = '<span><a href="#">' . $cname . '</a></span>';
      $link .= ' ' . l('[' . t('view this group') . ']', 'node/' . $cid);
      $togo .= '<li>' . $link;
      $togo .= og_subgroups_tree(og_subgroups_get_children($cid), $depth + 1);
      if ($show_members) {
        $togo .= og_subgroups_make_list(og_subgroups_get_users('links', $cid, 0), $depth);
      }
      $togo .= '</li>';
    }
  }
  $togo .= "</ul>";
  return $togo;
}
function og_subgroups_make_list($array, $depth = 0) {
  if ($depth == 0) {
    $togo = "<ul>";
  }
  else {
    $togo = "<ul style='display:none'>";
  }
  foreach ($array as $i) {
    $togo .= "<li class=leaf>{$i}</li>";
  }
  $togo .= "</ul>";
  return $togo;
}
function og_subgroups_save_family($this_node, $family_type) {
  $tree = og_subgroups_get_family($this_node, $family_type);
  if ($tree) {
    $sql = "DELETE FROM {og_ancestry} WHERE nid = %d";
    db_query($sql, $this_node->nid);
    foreach ($tree as $this_tree) {
      $this_tree_node = node_load($this_tree['nid']);
      if (!og_is_omitted_type($this_tree_node->type)) {
        $sql = "INSERT INTO {og_ancestry} (nid, group_nid, is_public) VALUES (%d, %d, %d)";
        $gid = $this_tree['nid'];
        db_query($sql, $this_node->nid, $gid, $this_node->og_public);
      }
      if (!empty($this_tree[$family_type])) {
        foreach ($this_tree[$family_type] as $this_key => $this_value) {
          $my_member = node_load($this_key);
          if (!og_is_omitted_type($my_member->type)) {
            $gid = $this_key;
            $sql = "INSERT INTO {og_ancestry} (nid, group_nid, is_public) VALUES (%d, %d, %d)";
            db_query($sql, $this_node->nid, $gid, $this_node->og_public);
          }
        }
      }
    }
  }
}
function og_subgroups_get_family($node, $relationship_type) {
  $func = og_subgroups_get_ . $relationship_type;
  $tree = array();
  if (is_array($node->parents)) {
    $parents = $node->parents;
  }
  else {
    $parents = $node->og_groups;
  }
  if ($parents) {
    foreach ($parents as $key => $value) {

      //Build the first level of the tree
      $tree[$value]['nid'] = $value;
      unset($tree[$value]['title']);
      $tree[$value]['title'] = $node->og_groups_names[$key];

      //when the node is not already in node og_groups this results in a blank title

      //build parents or children (or nothing)
      if ($relationship_type != 'none') {
        $tree[$value][$relationship_type] = $func($value);

        //og_subgroups_get_parents or og_subgroups_get_children
      }
      if (!empty($tree[$value][$relationship_type])) {
        foreach ($tree[$value][$relationship_type] as $this_member) {

          //unset the parents from the first level of the tree
          if ($this_member['nid'] != $tree[$value]['nid']) {

            //Don't unset the parent from the first level -- BUT DO UNSET IT FROM ITS OWN PARENT
            unset($tree[$this_member['nid']]);
          }
        }
      }
    }
  }
  return $tree;
}
function og_subgroups_edit_children_page($gid) {
  $node = node_load($gid);
  drupal_set_title(check_plain($node->title));
  $header = array(
    '',
    array(
      'data' => t('Group name'),
      'field' => 'name',
    ),
  );
  $result = db_query('SELECT og.nid, n.title FROM {og} og INNER JOIN {node} n where og.nid=n.nid AND og.nid <> %d', $gid);
  $options = array();
  while ($row = db_fetch_object($result)) {
    $options[$row->nid] = $row->title;
  }
  $selected = array();
  $selected = og_subgroups_get_children($gid);
  $form['subgroups'] = array(
    '#type' => 'checkboxes',
    '#default_value' => array_keys($selected),
    '#options' => $options,
    '#description' => t('Check all subgroups.'),
  );
  $form['op'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  $form['gid'] = array(
    '#type' => 'hidden',
    '#value' => $gid,
  );
  return $form;
}
function theme_og_subgroups_edit_children_page($form) {
  foreach ($form['subgroups'] as $gid => $val) {
    if (is_numeric($gid)) {
      $title = $form['subgroups'][$gid]['#title'];
      $form['subgroups'][$gid]['#title'] = "";
      $rows[] = array(
        drupal_render($form['subgroups'][$gid]),
        l($title, 'node/' . $gid),
      );
    }
  }
  $header = array(
    '',
    array(
      'data' => t('Group name'),
      'field' => 'name',
    ),
  );
  $output2 = drupal_render($form['subgroups']);
  $output = theme('table', $header, $rows);
  $output .= '<br>' . drupal_render($form) . '</br>';
  return $output2 . $output;
}
function og_subgroups_edit_children_page_submit($form_id, $values) {
  og_subgroups_save_children($values['gid'], $values['subgroups']);
  return 'node/' . $values['gid'];
}
function og_subgroups_save_children($gid, $subgroups) {

  // First delete group children from hierarchy
  $children = og_subgroups_get_children($gid);
  foreach ($children as $cid => $cname) {
    db_query('DELETE FROM {og_ancestry} WHERE nid=%d and group_nid=%d', $cid, $gid);
  }

  // Now add back each new child
  if (is_array($subgroups)) {
    foreach ($subgroups as $subgroup) {
      if ($subgroup != 0) {
        db_query('INSERT INTO {og_ancestry} (nid,group_nid,is_public) VALUES (%d,%d,0)', $subgroup, $gid);
      }
    }
  }
}
function og_subgroups_edit_members_page($gid) {
  $node = node_load($gid);
  drupal_set_title(check_plain($node->title));
  $header = array(
    '',
    array(
      'data' => t('Username'),
      'field' => 'name',
    ),
  );
  $result = db_query("SELECT u.uid, u.name, u.mail, u.picture FROM {users} u WHERE uid>0" . tablesort_sql($header));
  while ($user = db_fetch_object($result)) {
    $options[$user->uid] = $user;
  }
  $selected = og_subgroups_get_users('names', $node->nid);
  $form['members'] = array(
    '#type' => 'checkboxes',
    '#title' => NULL,
    '#default_value' => array_keys($selected),
    '#options' => $options,
    '#description' => t('Check anyone who is an immediate member of the group.  Members of subgroups need not be checked.'),
  );
  $form['op'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  $form['gid'] = array(
    '#type' => 'hidden',
    '#value' => $gid,
  );
  return $form;
}
function theme_og_subgroups_edit_members_page($form) {
  $header = array(
    '',
    array(
      'data' => t('Username'),
      'field' => 'name',
    ),
  );
  foreach ($form['members'] as $key => $val) {
    if (is_numeric($key)) {
      $username = theme('username', $val['#title']);
      $form['members'][$key]['#title'] = "";
      $rows[] = array(
        drupal_render($form['members'][$key]),
        $username,
        $email,
      );
    }
  }
  $output = theme('table', $header, $rows);
  $output2 = drupal_render($form['members']);
  $output .= '<br>' . drupal_render($form) . '</br>';
  return $output2 . $output;
}
function og_subgroups_edit_members_page_submit($form_id, $values) {
  og_subgroups_save_members($values['gid'], $values['members']);
  return 'node/' . $values['gid'];
}
function og_subgroups_save_members($gid, $members) {
  $new_group = array();
  if (is_array($members)) {
    foreach ($members as $uid => $active) {
      if ($active > 0) {
        og_save_subscription($gid, $uid, array(
          'is_active' => 1,
        ));
        $new_group[] = $uid;
      }
    }
  }
  $whole_group = og_subgroups_get_users('users', $gid);
  foreach ($whole_group as $uid => $val) {
    if (!in_array($uid, $new_group)) {
      og_delete_subscription($gid, $uid);
    }
  }
}
function og_subgroups_get_effective_groups($nodes = array(), $skip = array()) {
  $togo = array();
  if (!is_array($nodes)) {
    return array();
  }
  foreach ($nodes as $node) {
    if (!in_array(array_keys($nodes), array_keys($skip))) {
      $togo[$node['nid']] = $node;
      $togo2 = og_subgroups_get_effective_groups(og_subgroups_get_parents($node['nid']), $togo);
      if (is_array($togo2)) {
        foreach ($togo2 as $key => $val) {
          $togo[$key] = $val;
        }
      }
    }
  }
  return $togo;
}
function og_subgroups_get_parents($gid) {
  $result = db_query('SELECT n.title, n.type, n.status, oga.group_nid as nid, 1 as is_active FROM {og_ancestry} oga INNER JOIN {node} n WHERE oga.nid=%d AND oga.group_nid=n.nid', $gid);
  $parents = array();
  while ($row = db_fetch_array($result)) {
    if (og_is_group_type($row['type'])) {
      $parents[$row['nid']] = $row;
    }
  }
  return $parents;
}