--- sites/all/modules/cck/modules/fieldgroup/fieldgroup.module	Mon Dec 01 18:43:50 2008
+++ sites/all/modules/cck/modules/fieldgroup/fieldgroup.module	Tue Dec 23 11:26:22 2008
@@ -58,6 +58,7 @@
  * Implementation of hook_theme().
  */
 function fieldgroup_theme() {
+  //TODO do I need to add any theme functions?
   return array(
     'fieldgroup_simple' => array(
       'template' => 'fieldgroup',
@@ -160,6 +161,7 @@
   $form['settings']['display']['label'] = array('#type' => 'value', '#value' => $group['settings']['display']['label']);
   $form['weight'] = array('#type' => 'hidden', '#default_value' => $group['weight']);
   $form['group_name'] = array('#type' => 'hidden', '#default_value' => $group_name);
+  $form['parent_group_name'] = array('#type' => 'hidden', '#default_value' => $group['parent_group_name']);
 
   $form['#content_type'] = $content_type;
 
@@ -204,7 +206,10 @@
   $form_values = $form_state['values'];
   $content_type = $form['#content_type'];
   $group_name = $form['#group_name'];
-  fieldgroup_delete($content_type['type'], $group_name);
+  $parent_group_name = db_fetch_array(db_query("SELECT parent_group_name FROM {". fieldgroup_tablename() ."} WHERE group_name = '%s' and type_name = '%s'", $group_name, $content_type['type']));
+  $result = db_query("UPDATE {". fieldgroup_tablename() ."} SET parent_group_name = '%s' WHERE parent_group_name = '%s'", $parent_group_name['parent_group_name'], $group_name);
+  $result = db_query("UPDATE {". fieldgroup_fields_tablename() ."} SET group_name = '%s' WHERE group_name = '%s'", $parent_group_name['parent_group_name'], $group_name);
+  fieldgroup_delete($content_type['type'], $group_name);  
   drupal_set_message(t('The group %group_name has been removed.', array('%group_name' => $group_name)));
   $form_state['redirect'] = 'admin/content/node-type/'. $content_type['url_str'] .'/fields';
 }
@@ -221,23 +226,13 @@
       $groups_sorted = $data['groups_sorted'];
     }
     else {
-      $result = db_query("SELECT * FROM {". fieldgroup_tablename() ."} ORDER BY weight, group_name");
+      $result = db_query("SELECT type_name, group_name FROM {". fieldgroup_tablename() ."} ORDER BY type_name");
       $groups = array();
       $groups_sorted = array();
       while ($group = db_fetch_array($result)) {
-        $group['settings'] = unserialize($group['settings']);
-        $group['fields'] = array();
-        $groups[$group['type_name']][$group['group_name']] = $group;
+        $groups[$group['type_name']] = _fieldgroup_get_tree($group['type_name']);
         $groups_sorted[$group['type_name']][] = &$groups[$group['type_name']][$group['group_name']];
       }
-      //load fields
-      $result = db_query("SELECT nfi.*, ng.group_name FROM {". fieldgroup_tablename() ."} ng ".
- "INNER JOIN {". fieldgroup_fields_tablename() ."} ngf ON ngf.type_name = ng.type_name AND ngf.group_name = ng.group_name ".
- "INNER JOIN {". content_instance_tablename() ."} nfi ON nfi.field_name = ngf.field_name AND nfi.type_name = ngf.type_name ".
- "WHERE nfi.widget_active = 1 ORDER BY nfi.weight");
-      while ($field = db_fetch_array($result)) {
-        $groups[$field['type_name']][$field['group_name']]['fields'][$field['field_name']] = $field;
-      }
       cache_set('fieldgroup_data', array('groups' => $groups, 'groups_sorted' => $groups_sorted), content_cache_tablename());
     }
   }
@@ -250,6 +245,69 @@
   return $sorted ? $groups_sorted[$content_type] : $groups[$content_type];
 }
 
+/**
+ * create a tree of fieldgroups for nesting them
+ */
+function _fieldgroup_get_tree($type_name, $parent_group_name = '', $depth = -1, $max_depth = null) {
+  static $children, $parents, $groups;
+
+  $depth++;
+  // We cache trees, so it's not CPU-intensive to call get_tree() on a term
+  // and its children, too.
+  if (!isset($children[$type_name])) {
+    $children[$type_name] = array();
+
+    $s = "SELECT * FROM {". fieldgroup_tablename() ."} WHERE type_name='%s' ORDER BY weight";
+    $r = db_query($s, $type_name);
+    while ($group = db_fetch_array($r)) {
+      $children[$type_name][$group['parent_group_name']][] = $group['group_name'];
+      $parents[$type_name][$group['group_name']][] = $group['parent_group_name'];
+      $groups[$type_name][$group['group_name']] = $group;
+      $groups[$type_name][$group['group_name']]['fields'] = array();
+      $groups[$type_name][$group['group_name']]['settings'] = unserialize($group['settings']);
+    }
+    //load fields
+    $result = db_query("SELECT nfi.*, ng.group_name FROM {". fieldgroup_tablename() ."} ng ".
+        "INNER JOIN {". fieldgroup_fields_tablename() ."} ngf ON ngf.type_name = ng.type_name AND ngf.group_name = ng.group_name ".
+        "INNER JOIN {". content_instance_tablename() ."} nfi ON nfi.field_name = ngf.field_name AND nfi.type_name = ngf.type_name ".
+        "WHERE nfi.widget_active = 1 ORDER BY nfi.weight");
+    while ($field = db_fetch_array($result)) {
+      $groups[$field['type_name']][$field['group_name']]['fields'][$field['field_name']] = $field;
+    }
+  }
+
+  $max_depth = (is_null($max_depth)) ? count($children[$type_name]) : $max_depth;
+  if (isset($children[$type_name][$parent_group_name])) {
+    foreach ($children[$type_name][$parent_group_name] as $child_group_name) {
+      if ($max_depth > $depth) {
+        $group = $groups[$type_name][$child_group_name];
+        $group['depth'] = $depth;
+        $group['parents'] = $parents[$type_name][$child_group_name];
+        $tree[$group['group_name']] = $group;
+        if ($children[$type_name][$child_group_name]) {
+          $tree = array_merge($tree, _fieldgroup_get_tree($type_name, $child_group_name, $depth, $max_depth));
+        }
+      }
+    }
+  }
+  return $tree ? $tree : array();
+}
+
+/**
+ * go through a set of fieldgroups and construct a simple representation of their hierarchy
+ */
+function _fieldgroup_plain_tree($items) {
+  $rows = array();
+  $rows[''] = '<'. t('none') .'>';
+  foreach ($items as $item) {
+    $group_name = $item['group_name'];
+    $label = t($item['label']);
+    if ($group_name) {
+      $rows[$group_name] = str_repeat('--', $item['depth']) . ' ' . $label;
+    }
+  }
+  return $rows;
+}
 
 function _fieldgroup_groups_label($content_type) {
   $groups = fieldgroup_groups($content_type);
@@ -265,53 +323,114 @@
   return db_result(db_query("SELECT group_name FROM {". fieldgroup_fields_tablename() ."} WHERE type_name = '%s' AND field_name = '%s'", $content_type, $field_name));
 }
 
+function _fieldgroup_add_group_to_form(&$form, &$form_state, $form_id, $element, $group_name, $group, $groups) {
+  $element = array(
+    '#type' => 'fieldset',
+    '#title' => check_plain(t($group['label'])),
+    '#collapsed' => $group['settings']['form']['style'] == 'fieldset_collapsed',
+    '#collapsible' => in_array($group['settings']['form']['style'], array('fieldset_collapsed', 'fieldset_collapsible')),
+    '#weight' => $group['weight'],
+    '#description' => content_filter_xss(t($group['settings']['form']['description'])),
+    '#attributes' => array('class' => strtr($group['group_name'], '_', '-')),
+  );
+  $has_accessible_field = FALSE;
+  foreach ($group['fields'] as $field_name => $field) {
+    if (isset($form[$field_name])) {
+      $element[$field_name] = $form[$field_name];
+      //Track whether this group has any accessible fields within it.
+      if (!isset($form[$field_name]['#access']) || $form[$field_name]['#access'] !== FALSE) {
+        $has_accessible_field = TRUE;
+      }
+      unset($form[$field_name]);
+    }
+  }
+  if (!empty($group['fields']) && !element_children($form[$group_name])) {
+    //hide the fieldgroup, because the fields are hidden too
+    unset($element[$group_name]);
+  }
+  
+  if (!$has_accessible_field) {
+    // Hide the fieldgroup, because the fields are inaccessible.
+    $form[$group_name]['#access'] = FALSE;
+  }
+
+  // Allow other modules to alter the form.
+  // Can't use module_invoke_all because we want
+  // to be able to use a reference to $form and $form_state.
+  foreach (module_implements('fieldgroup_form') as $module) {
+    $function = $module .'_fieldgroup_form';
+    $function($form, $form_state, $form_id, $group);
+  }
+
+  $parent_group_name = $group_name;
+  foreach ($groups as $group_name => $group) {
+    if ($group['parent_group_name'] == $parent_group_name) {
+      $element[$group_name] = _fieldgroup_add_group_to_form($form, $form_state, $form_id, $element[$group_name], $group_name, $group, $groups);
+    }
+  }
+
+  return $element;
+}
+
 /**
  * Implementation of hook_form_alter()
  */
 function fieldgroup_form_alter(&$form, $form_state, $form_id) {
   if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
-    foreach (fieldgroup_groups($form['type']['#value']) as $group_name => $group) {
-      $form[$group_name] = array(
-        '#type' => 'fieldset',
-        '#title' => check_plain(t($group['label'])),
-        '#collapsed' => $group['settings']['form']['style'] == 'fieldset_collapsed',
-        '#collapsible' => in_array($group['settings']['form']['style'], array('fieldset_collapsed', 'fieldset_collapsible')),
-        '#weight' => $group['weight'],
-        '#description' => content_filter_xss(t($group['settings']['form']['description'])),
-        '#attributes' => array('class' => strtr($group['group_name'], '_', '-')),
-      );
-
-      $has_accessible_field = FALSE;
-      foreach ($group['fields'] as $field_name => $field) {
-        if (isset($form[$field_name])) {
-          $form[$group_name][$field_name] = $form[$field_name];
-          // Track whether this group has any accessible fields within it.
-          if (!isset($form[$field_name]['#access']) || $form[$field_name]['#access'] !== FALSE) {
-            $has_accessible_field = TRUE;
-          }
-          unset($form[$field_name]);
+    if ((arg(0)=='node' && arg(1)=='add') || (arg(0)=='node' && is_numeric(arg(1)) && arg(2)=='edit')) {
+      $groups = fieldgroup_groups($form['type']['#value']);
+      foreach ($groups as $group_name => $group) {
+        //drupal_set_message('<pre>Name:'.print_r($group_name, 1).'</pre>');
+        //drupal_set_message('<pre>Group:'.print_r($group, 1).'</pre>');
+        $parent_group_name = $group['parent_group_name'];
+        if ($parent_group_name) {
+          $groups[$group_name]['weight'] = $groups[$parent_group_name]['weight'] + abs($groups[$group_name]['depth'] * .1) + abs($groups[$group_name]['weight'] * .01);
         }
       }
-      if (!empty($group['fields']) && !element_children($form[$group_name])) {
-        //hide the fieldgroup, because the fields are hidden too
-        unset($form[$group_name]);
-      }
-
-      if (!$has_accessible_field) {
-        // Hide the fieldgroup, because the fields are inaccessible.
-        $form[$group_name]['#access'] = FALSE;
+      foreach ($groups as $group_name => $group) {
+        $parent_group_name = $group['parent_group_name'];
+        if (!$parent_group_name) {
+          $form[$group_name] = _fieldgroup_add_group_to_form($form, $form_state, $form_id, $form[$group_name], $group_name, $group, $groups);
+        }
       }
-
-      // Allow other modules to alter the form.
-      // Can't use module_invoke_all because we want
-      // to be able to use a reference to $form and $form_state.
-      foreach (module_implements('fieldgroup_form') as $module) {
-        $function = $module .'_fieldgroup_form';
-        $function($form, $form_state, $form_id, $group);
+    }
+    else {
+      //Sort groups by weight
+      foreach ($groups as $group_name => $group) {
+        $form[$group_name] = array(
+            '#type' => 'fieldset',
+            '#title' => t($group['label']),
+            '#collapsed' => $group['settings']['form']['style'] == 'fieldset_collapsed',
+            '#collapsible' => in_array($group['settings']['form']['style'], array('fieldset_collapsed', 'fieldset_collapsible')),
+            '#weight' => $group['weight'],
+            '#description' => t($group['settings']['form']['description']),
+            '#attributes' => array('class' => strtr($group['group_name'], '_', '-')),
+            );
+        foreach ($group['fields'] as $field_name => $field) {
+          if (isset($form[$field_name])) {
+            $form[$group_name][$field_name] = $form[$field_name];
+            unset($form[$field_name]);
+          }
+        }
+        if (!empty($group['fields']) && !element_children($form[$group_name])) {
+          //hide the fieldgroup, because the fields are hidden too
+          unset($form[$group_name]);
+        }
+        
+        if (!$has_accessible_field) {
+          // Hide the fieldgroup, because the fields are inaccessible.
+          $form[$group_name]['#access'] = FALSE;
+        }
+        
+        // Allow other modules to alter the form.
+        // Can't use module_invoke_all because we want
+        // to be able to use a reference to $form and $form_state.
+        foreach (module_implements('fieldgroup_form') as $module) {
+          $function = $module .'_fieldgroup_form';
+          $function($form, $form_state, $form_id, $group);
+        }
       }
-
     }
-
   }
   // The group is only added here so it will appear in the export
   // when using Content Copy.
@@ -435,6 +554,8 @@
   // Parse incoming rows.
   $add_field_rows = array('_add_new_field', '_add_existing_field');
   $field_rows = array_merge($form['#fields'], $add_field_rows);
+  $add_group_rows = array($new_group_name);
+  $group_rows = array_merge($form['#groups'], $add_group_rows);
   foreach ($form_values as $key => $values) {
     // If 'field' row: update field parenting.
     if (in_array($key, $field_rows)) {
@@ -458,18 +579,71 @@
       // TODO: check the parent group does exist ?
       fieldgroup_update_fields(array('field_name' => $key, 'group' => $parent, 'type_name' => $type_name));
     }
+  }
 
-    // If 'group' row:  update groups weights
+  $top_groups = array();
+  $parent_child_list = array();
+  reset($form_values);
+  foreach ($form_values as $key => $values) {
+    if (in_array($key, $group_rows)) {
+      if (empty($values['parent']) || $values['parent'] == $values['hidden_name']) {
+        $top_groups[$values['weight']][] = $values['hidden_name'];//this allows for groups that are the same weight after submission
+      }
+      else {
+        $parent_child_list[$values['parent']][] = $values['hidden_name'];
+      }
+    }
+  }
+  $low_index = 0;
+  foreach ($top_groups as $weight => $data) {
+    if ($weight < $low_index) {
+      $low_index = $weight;
+    }      
+    sort($top_groups[$weight]);
+  }
+  //we don't want top level groups to have the same weight as each other, so let's change that here
+  $top_groups_reordered = array($low_index => $top_groups[$low_index][0]);
+  foreach ($top_groups as $weight => $data) {
+    foreach ($data as $index => $name) {
+      if (($weight != $low_index) || ($index != 0)) {
+        $top_groups_reordered[] = $name;
+      }
+    }
+  }
+  foreach ($top_groups_reordered as $weight => $name) {
+    $form_state['values'][$name]['weight'] = $weight;
+  }
+  foreach ($top_groups_reordered as $index => $name) {
+    fieldgroup_parent_child_recurse($form_state, $name, $index, $parent_child_list);
+  }
+  //drupal_set_message('<pre>Values: ' . print_r($form_state['values'], TRUE) . '</pre>');
+  foreach ($form_state['values'] as $key => $values) {
+    // If 'group' row:  update groups weights and parent
     // (possible newly created group has already been taken care of).
-    elseif (in_array($key, $form['#groups'])) {
-      db_query("UPDATE {". fieldgroup_tablename() ."} SET weight = %d WHERE type_name = '%s' AND group_name = '%s'",
-        $values['weight'], $type_name, $key);
+    if (in_array($key, $group_rows)) {
+      $parent = ($values['parent'] == '_add_new_group' && isset($new_group_name)) ? $new_group_name : $values['parent'];
+      $weight = $values['weight'];
+      //drupal_set_message('<pre>Key: ' . print_r($key, TRUE) . '</pre>');
+      //drupal_set_message('<pre>Parent: ' . print_r($parent, TRUE) . '</pre>');
+      //drupal_set_message('<pre>Weight: ' . print_r($weight, TRUE) . '</pre>');
+      db_query("UPDATE {". fieldgroup_tablename() ."} SET weight = %d, parent_group_name = '%s' WHERE type_name = '%s' AND group_name = '%s'", $weight, $parent, $type_name, $key);
     }
   }
-
+  
   cache_clear_all('fieldgroup_data', content_cache_tablename());
 }
 
+function fieldgroup_parent_child_recurse(&$form_state, $name, $index, $list) {
+  //drupal_set_message('<pre>Name: ' . print_r($name, TRUE) . '</pre>');
+  //drupal_set_message('<pre>Index - weight: ' . print_r($index, TRUE) . '</pre>');
+  if (isset($list[$name])) {
+    foreach ($list[$name] as $index2 => $group_name) {
+      $form_state['values'][$group_name]['weight'] = $index;
+      fieldgroup_parent_child_recurse($form_state, $group_name, $index, $list);
+    }
+  }
+}
+
 function field_group_default_settings($group_type) {
   $settings = array(
     'form' => array('style' => 'fieldset', 'description' => ''),
@@ -609,7 +783,7 @@
   return $content;
 }
 
-/*
+/**
  * Get the group name for a field.
  * If the field isn't in a group, FALSE will be returned.
  * @return The name of the group, or FALSE.
@@ -681,15 +855,15 @@
 
   if (!isset($groups[$group['group_name']])) {
     // Accept group name from programmed submissions if valid.
-    db_query("INSERT INTO {". fieldgroup_tablename() ."} (group_type, type_name, group_name, label, settings, weight)".
-      " VALUES ('%s', '%s', '%s', '%s', '%s', %d)", $group['group_type'], $type_name, $group['group_name'], $group['label'], serialize($group['settings']), $group['weight']);
+    db_query("INSERT INTO {". fieldgroup_tablename() ."} (parent_group_name, group_type, type_name, group_name, label, settings, weight)".
+      " VALUES ('%s','%s', '%s', '%s', '%s', '%s', %d)", $group['parent_group_name'], $group['group_type'], $type_name, $group['group_name'], $group['label'], serialize($group['settings']), $group['weight']);
     cache_clear_all('fieldgroup_data', content_cache_tablename());
     return SAVED_NEW;
   }
   else {
-    db_query("UPDATE {". fieldgroup_tablename() ."} SET group_type = '%s', label = '%s', settings = '%s', weight = %d ".
-             "WHERE type_name = '%s' AND group_name = '%s'",
-             $group['group_type'], $group['label'], serialize($group['settings']), $group['weight'], $type_name, $group['group_name']);
+    db_query("UPDATE {". fieldgroup_tablename() ."} SET parent_group_name =  '%s', group_type = '%s', label = '%s', settings = '%s', weight = %d ".
+        "WHERE type_name = '%s' AND group_name = '%s'",
+        $group['parent_group_name'], $group['group_type'], $group['label'], serialize($group['settings']), $group['weight'], $type_name, $group['group_name']);
     cache_clear_all('fieldgroup_data', content_cache_tablename());
     return SAVED_UPDATED;
   }
@@ -740,7 +914,9 @@
       $element['#attributes']['class'] .= ' collapsed';
     }
   }
-  return '<fieldset'. drupal_attributes($element['#attributes']) .'>'. ($element['#title'] ? '<legend>'. $element['#title'] .'</legend>' : '') . (isset($element['#description']) && $element['#description'] ? '<div class="description">'. $element['#description'] .'</div>' : '') . (!empty($element['#children']) ? $element['#children'] : '') . (isset($element['#value']) ? $element['#value'] : '') ."</fieldset>\n";
+  return '<fieldset'. drupal_attributes($element['#attributes']) .'>'. ($element['#title'] ? '<legend>'. $element['#title'] .'</legend>' : '')
+    . (isset($element['#description']) && $element['#description'] ? '<div class="description">'. $element['#description'] .'</div>' : '') 
+    . (!empty($element['#children']) ? $element['#children'] : '') . (isset($element['#value']) ? $element['#value'] : '') ."</fieldset>\n";
 }
 
 
@@ -759,6 +935,7 @@
 function fieldgroup_preprocess_fieldgroup_simple(&$vars) {
   $element = $vars['element'];
 
+  $vars['parent_group_name'] = $element['#parent_group_name'];
   $vars['group_name'] = $element['#group_name'];
   $vars['group_name_css'] = strtr($element['#group_name'], '_', '-');
   $vars['label'] = isset($element['#title']) ? $element['#title'] : '';;
