diff -Nurp ../cck.orig/includes/content.admin.inc ./includes/content.admin.inc --- ../cck.orig/includes/content.admin.inc 2010-07-30 11:09:25.000000000 -0500 +++ ./includes/content.admin.inc 2010-07-30 11:11:04.000000000 -0500 @@ -147,6 +147,7 @@ function content_field_overview_form(&$f if (module_exists('fieldgroup')) { $groups = fieldgroup_groups($type['type']); $group_types = fieldgroup_types(); + $plain_tree = _fieldgroup_plain_tree($groups); $group_options = _fieldgroup_groups_label($type['type']); // Add the ability to group under the newly created row. $group_options['_add_new_group'] = '_add_new_group'; @@ -194,6 +195,8 @@ function content_field_overview_form(&$f // Groups. foreach ($groups as $name => $group) { + $current_group_options = $plain_tree; + unset($current_group_options[$name]); $weight = $group['weight']; $form[$name] = array( 'label' => array('#value' => check_plain($group['label'])), @@ -202,17 +205,23 @@ function content_field_overview_form(&$f 'configure' => array('#value' => l(t('Configure'), 'admin/content/node-type/'. $type['url_str'] .'/groups/'. $group['group_name'])), 'remove' => array('#value' => l(t('Remove'), 'admin/content/node-type/'. $type['url_str'] .'/groups/'. $group['group_name'] .'/remove')), 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), - 'parent' => array('#type' => 'hidden', '#default_value' => ''), + 'parent' => array('#type' => 'select', '#options' => $current_group_options, '#default_value' => ''), + 'prev_parent' => array('#type' => 'hidden', '#value' => ''), 'hidden_name' => array('#type' => 'hidden', '#default_value' => $group['group_name']), - '#root' => TRUE, '#row_type' => 'group', 'group' => array('#type' => 'value', '#value' => $group), ); // Adjust child fields rows. - foreach ($group['fields'] as $field_name => $field) { - $form[$field_name]['parent']['#default_value'] = $name; - $form[$field_name]['prev_parent']['#value'] = $name; + if (isset($group['fields'])) { + foreach ($group['fields'] as $field_name => $field) { + $form[$field_name]['parent']['#default_value'] = $name; + $form[$field_name]['prev_parent']['#value'] = $name; + } } + // Adjust child group rows + $form[$name]['parent']['#default_value'] = $group['parent_group_name']; + $form[$name]['prev_parent']['#value'] = $group['parent_group_name']; + $form['#group_rows'][] = $name; $weights[] = $weight; } @@ -317,6 +326,9 @@ function content_field_overview_form(&$f // Additional row : add new group. if (!empty($group_types)) { + $current_group_options = $group_options; + $options = fieldgroup_types(); + unset($current_group_options[_add_new_group]); $weight++; $name = '_add_new_group'; $form[$name] = array( @@ -346,9 +358,9 @@ function content_field_overview_form(&$f '#value' => 'standard', ), 'weight' => array('#type' => 'textfield', '#default_value' => $weight, '#size' => 3), - 'parent' => array('#type' => 'hidden', '#default_value' => ''), + 'parent' => array('#type' => 'select', '#options' => $current_group_options, '#default_value' => ''), + 'prev_parent' => array('#type' => 'hidden', '#value' => ''), 'hidden_name' => array('#type' => 'hidden', '#default_value' => $name), - '#root' => TRUE, '#add_new' => TRUE, '#row_type' => 'add_new_group', ); @@ -662,6 +674,7 @@ function content_display_overview_form(& $form[$name] = array( 'human_name' => array('#value' => check_plain($group['label'])), 'weight' => array('#type' => 'value', '#value' => $weight), + 'parent' => array('#type' => 'value', '#value' => ''), ); if ($contexts_selector == 'basic') { $form[$name]['label'] = array( @@ -686,6 +699,8 @@ function content_display_overview_form(& foreach ($group['fields'] as $field_name => $field) { $form[$field_name]['parent']['#value'] = $name; } + $form[$name]['parent']['#value'] = $group['parent_group_name']; + $form[$name]['group']['#value']['depth'] = $group['depth']; } $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); @@ -1667,6 +1682,31 @@ function _content_overview_order(&$form, } } } + // Nested fieldgroup + if (module_exists('fieldgroup')) { + // readjust the depth and parenting of fieldgroup + $nested = array(); + foreach ($group_rows as $name) { + if ($parent = $form[$name]['parent']['#value']) { + $form[$name]['#depth'] = $form[$parent]['#depth'] + 1; + if (array_key_exists($parent, $nested)){ + $nested[$parent][$name] = $dummy[$name]; + $nested[$name] = & $nested[$parent][$name]; + } else { + $dummy[$parent][$name] = $dummy[$name]; + $nested[$name] = & $dummy[$parent][$name]; + } + unset($dummy[$name]); + } + } + // readjust the depth + foreach ($field_rows as $name) { + if ($parent = $form[$name]['parent']['#value']) { + $form[$name]['#depth'] = $form[$parent]['#depth'] + 1; + } + } + } + return $dummy ? explode(' ', trim(drupal_render($dummy))) : array(); } diff -Nurp ../cck.orig/includes/content.node_form.inc ./includes/content.node_form.inc --- ../cck.orig/includes/content.node_form.inc 2010-07-30 11:09:25.000000000 -0500 +++ ./includes/content.node_form.inc 2010-07-30 11:11:04.000000000 -0500 @@ -325,12 +325,7 @@ function content_add_more_js($type_name_ drupal_alter('form', $form_element, array(), 'content_add_more_js'); // Add the new element at the right place in the (original, unbuilt) form. - if (module_exists('fieldgroup') && ($group_name = _fieldgroup_field_get_group($type['type'], $field_name))) { - $form[$group_name][$field_name] = $form_element[$field_name]; - } - else { - $form[$field_name] = $form_element[$field_name]; - } + content_set_form_element($field_name, $type['type'], $form, $form_element[$field_name]); // Save the new definition of the form. $form_state['values'] = array(); @@ -348,7 +343,8 @@ function content_add_more_js($type_name_ $form = form_builder($_POST['form_id'], $form, $form_state); // Render the new output. - $field_form = (!empty($group_name)) ? $form[$group_name][$field_name] : $form[$field_name]; + $field_form = content_get_form_element($field_name, $type['type'], $form); + // We add a div around the new content to receive the ahah effect. $field_form[$delta]['#prefix'] = '
'. (isset($field_form[$delta]['#prefix']) ? $field_form[$delta]['#prefix'] : ''); $field_form[$delta]['#suffix'] = (isset($field_form[$delta]['#suffix']) ? $field_form[$delta]['#suffix'] : '') .'
'; @@ -372,3 +368,74 @@ function content_add_more_js($type_name_ print drupal_to_js(array('status' => TRUE, 'data' => $output)); exit; } + +/** + * Store an element into a form. + * + * @param $name + * The field name. + * @param $type + * The content type where the field instance belongs to. + * @param $form + * The form to store this field element into. + * @param $element + * The form element to store. + */ +function content_set_form_element($name, $type, &$form, $element, $is_group = FALSE) { + if (module_exists('fieldgroup') && ($parents = _fieldgroup_field_get_parents($type, $name, $is_group))) { + foreach (module_implements('fieldgroup_parents_alter') as $module) { + $parents = call_user_func($module .'_fieldgroup_parents_alter', $form, $parents, $type, $name); + } + $reference = &$form; + if ($is_group) { + array_shift($parents); + $parents = array_reverse($parents); + } + else { + $parents = array_reverse($parents); + } + + foreach (array_values($parents) as $group_name) { + $reference = &$reference[$group_name]; + } + + $reference[$name] = $element; + + } + else { + $form[$name] = $element; + } +} + +/** + * Retrieve an element from a form. + * + * @param $name + * The field name. + * @param $type + * The content type where the field instance belongs to. + * @param $form + * The form to retrieve this field element from. + */ +function content_get_form_element($name, $type, $form, $is_group = FALSE) { + if (module_exists('fieldgroup') && ($parents = _fieldgroup_field_get_parents($type, $name, $is_group))) { + foreach (module_implements('fieldgroup_parents_alter') as $module) { + $parents = call_user_func($module .'_fieldgroup_parents_alter', $form, $parents, $type, $name); + } + $reference = &$form; + if ($is_group) { + array_shift($parents); + $parents = array_reverse($parents); + } + else { + $parents = array_reverse($parents); + } + + foreach (array_values($parents) as $group_name) { + $reference = &$reference[$group_name]; + } + + return $reference[$name]; + } + return $form[$name]; +} diff -Nurp ../cck.orig/modules/fieldgroup/fieldgroup.install ./modules/fieldgroup/fieldgroup.install --- ../cck.orig/modules/fieldgroup/fieldgroup.install 2010-07-30 11:09:25.000000000 -0500 +++ ./modules/fieldgroup/fieldgroup.install 2010-07-30 11:11:04.000000000 -0500 @@ -52,6 +52,7 @@ function fieldgroup_schema() { 'group_type' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => 'standard'), 'type_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), 'group_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'parent_group_name' => array('type' => 'varchar', 'length' => 32, 'not null' => FALSE, 'default' => ''), 'label' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), 'settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE), 'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), @@ -313,4 +314,16 @@ function fieldgroup_update_6007() { $ret = array(); $ret[] = update_sql("DELETE FROM {content_group_fields} WHERE (field_name, type_name) NOT IN (SELECT field_name, type_name FROM {content_node_field_instance})"); return $ret; +} + +/** + * allow for nesting of fieldgroups + */ +function fieldgroup_update_6008() { + if ($abort = content_check_update('fieldgroup')) { + return $abort; + } + $ret = array(); + db_add_field($ret, 'content_group', 'parent_group_name', array('type' => 'varchar', 'length' => 32, 'not null' => FALSE, 'default' => '')); + return $ret; } \ No newline at end of file diff -Nurp ../cck.orig/modules/fieldgroup/fieldgroup.module ./modules/fieldgroup/fieldgroup.module --- ../cck.orig/modules/fieldgroup/fieldgroup.module 2010-07-30 11:09:25.000000000 -0500 +++ ./modules/fieldgroup/fieldgroup.module 2010-07-30 11:13:25.000000000 -0500 @@ -166,6 +166,7 @@ function fieldgroup_group_edit_form(&$fo $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; @@ -210,6 +211,9 @@ function fieldgroup_remove_group_submit( $form_values = $form_state['values']; $content_type = $form['#content_type']; $group_name = $form['#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' AND type_name = '%s'", $parent_group_name['parent_group_name'], $group_name, $content_type['type']); + $result = db_query("UPDATE {". fieldgroup_fields_tablename() ."} SET group_name = '%s' WHERE group_name = '%s' AND type_name = '%s'", $parent_group_name['parent_group_name'], $group_name, $content_type['type']); 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'; @@ -228,45 +232,14 @@ function fieldgroup_groups($content_type $groups_sorted = $data['groups_sorted']; } else { - $result = db_query("SELECT * FROM {". fieldgroup_tablename() ."} ORDER BY weight, group_name"); + $result = db_query("SELECT * FROM {". fieldgroup_tablename() ."} ORDER BY type_name, weight"); $groups = array(); $groups_sorted = array(); while ($group = db_fetch_array($result)) { - $group['settings'] = unserialize($group['settings']); - $group['fields'] = array(); - - // Allow external modules to translate field group strings. - $group_strings = array( - 'label' => $group['label'], - 'form_description' => $group['settings']['form']['description'], - 'display_description' => $group['settings']['display']['description'], - ); - drupal_alter('content_fieldgroup_strings', $group_strings, $group['type_name'], $group['group_name']); - $group['label'] = $group_strings['label']; - $group['settings']['form']['description'] = $group_strings['form_description']; - $group['settings']['display']['description'] = $group_strings['display_description']; - - $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)) { - // Allow external modules to translate field strings. - $field_strings = array( - 'widget_label' => $field['label'], - 'widget_description' => $field['description'], - ); - drupal_alter('content_field_strings', $field_strings, $field['type_name'], $field['field_name']); - $field['label'] = $field_strings['widget_label']; - $field['description'] = $field_strings['widget_description']; - - $groups[$field['type_name']][$field['group_name']]['fields'][$field['field_name']] = $field; - } - cache_set('fieldgroup_data:'. $language->language, array('groups' => $groups, 'groups_sorted' => $groups_sorted), content_cache_tablename()); + cache_set('fieldgroup_data', array('groups' => $groups, 'groups_sorted' => $groups_sorted), content_cache_tablename()); } } if (empty($content_type)) { @@ -278,6 +251,89 @@ function fieldgroup_groups($content_type 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)) { + $group['settings'] = unserialize($group['settings']); + $group['fields'] = array(); + + // Allow external modules to translate field group strings. + $group_strings = array( + 'label' => $group['label'], + 'form_description' => $group['settings']['form']['description'], + 'display_description' => $group['settings']['display']['description'], + ); + drupal_alter('content_fieldgroup_strings', $group_strings, $type_name, $group['group_name']); + $group['label'] = $group_strings['label']; + $group['settings']['form']['description'] = $group_strings['form_description']; + $group['settings']['display']['description'] = $group_strings['display_description']; + + $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; + } + //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; + + // Unserialize arrays. + foreach (array('widget_settings', 'display_settings', 'global_settings', 'db_columns') as $key) { + $groups[$field['type_name']][$field['group_name']]['fields'][$field['field_name']] = (!empty($groups[$field['type_name']][$field['group_name']]['fields'][$field['field_name']][$key])) ? (array) unserialize($groups[$field['type_name']][$field['group_name']]['fields'][$field['field_name']][$key]) : array(); + } + + // For fields inside of groups, use the weight given by fieldgroup + $groups[$field['type_name']][$field['group_name']]['fields'][$field['field_name']]['weight'] = $field['weight']; + } + } + + $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); @@ -293,51 +349,157 @@ function _fieldgroup_field_get_group($co 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_field_get_parents($content_type, $name, $is_group = FALSE) { + $counter = 0; + if ($is_group) { + $parents[$counter] = $name; + } + else { + if ($result = db_result(db_query("SELECT group_name FROM {". fieldgroup_fields_tablename() ."} WHERE type_name = '%s' AND field_name = '%s'", $content_type, $name))) { + $parents[$counter] = $result; + } + } + while ($result = db_result(db_query("SELECT parent_group_name FROM {". fieldgroup_tablename() ."} WHERE type_name = '%s' AND group_name = '%s'", $content_type, $parents[$counter]))) { + $counter++; + $parents[$counter] = $result; + } + return $parents; +} + +function _fieldgroup_add_group_to_form(&$form, &$form_state, $form_id, $group_name, $group, $groups) { + $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'], + '#depth' => $group['depth'], + '#group_parent' => $group['parent_group_name'], + '#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[$field_name]['#weight'] = $field['weight']; + $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 (!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; + } + else { + //cascade visibility up + $form[$group_name]['#access'] = TRUE; + } + + // 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); + } +} + + +/** + * This is function fieldgroup_order_fields_and_groups + * + * @param array $group_rows An empty array that we will fill. + * @param array $groups All of the info we need about all of the groups for the content type we're working on. + * @param array $field_check_off This contains the fields. We will unset them as we process them. + * + */ +function fieldgroup_order_fields_groups(&$group_rows, &$groups, &$field_check_off) { + $max_depth = 0; + foreach ($group_rows as $name) { + $depth = $groups[$name]['depth']; + if ($depth > $max_depth) { + $max_depth = $depth; + } + $parent = $groups[$name]['parent_group_name']; + + //run through the fields and come up with new weights for display purposes + if (isset($groups[$name]['fields'])) { + foreach ($groups[$name]['fields'] as $name2 => $elements) { + $depth2 = $groups[$name]['depth'] + 1; + $groups[$name]['fields'][$name2]['depth'] = $depth2; + if (in_array($name2, $field_check_off)) { + $index = array_search($name2, $field_check_off); + unset($field_check_off[$index]); + } + } + } + } + return $max_depth; +} + /** * 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; + $group_rows = array(); + $field_rows = array(); + + //prepare data that will make this easier + $groups = fieldgroup_groups($form['type']['#value']); + if (!empty($groups)) { + foreach ($groups as $name => $more) { + $group_rows[] = $name; + } + } + + $fields = $form['#field_info']; + if (!empty($fields)) { + foreach ($fields as $name => $more) { + $field_rows[] = $name; + } + } + + $max_depth = fieldgroup_order_fields_groups($group_rows, $groups, $field_rows); + + //cover the top level fields that aren't in fieldgroups + if (isset($field_rows)) { + foreach ($field_rows as $name) { + $form[$name]['#depth'] = 0; + } + } + + //now that we have the order of things as we want them, let's create the fieldsets for the fieldgroups + foreach ($groups as $group_name => $group) { + _fieldgroup_add_group_to_form($form, $form_state, $form_id, $group_name, $group, $groups); + } + + //reorder the groups from the inside-out in order to avoid a recursive function + while ($max_depth >= 0) { + foreach ($group_rows as $name) { + if ($form[$name]['#depth'] == $max_depth) { + $parent = $form[$name]['#group_parent']; + if (isset($parent) && $parent != '') { + $form[$parent][$name] = $form[$name]; + if ($form[$name]['#access']) { + $form[$parent]['#access'] = TRUE; + } + unset($form[$name]); + $index = array_search($name, $group_rows); + unset($group_rows[$index]); } - 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); - } - + $max_depth--; } } @@ -463,6 +625,10 @@ function fieldgroup_field_overview_form_ // 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)) { @@ -486,12 +652,15 @@ function fieldgroup_field_overview_form_ // 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 + } + + 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']; + 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); } } @@ -519,6 +688,18 @@ function fieldgroup_display_overview_for foreach ($form_values as $key => $values) { if (in_array($key, $form['#groups'])) { $group = $groups[$key]; + // Multigroup data (namely, subgroup data) ends up in the settings array as well + // Prevent this data from ending up inside of ['settings']['display'] + if (is_array($values['settings']) && array_key_exists('multigroup', $values['settings'])){ + if (is_array($group['settings']['multigroup'])){ + foreach ($values['settings']['multigroup'] as $mg_key => $mg_values){ + $group['settings']['multigroup'][$mg_key] = $values['settings']['multigroup'][$mg_key]; + } + } else { + $group['settings']['multigroup'] = $values['settings']['multigroup']; + } + unset($values['settings']['multigroup']); + } // We have some numeric keys here, so we can't use array_merge. $group['settings']['display'] = $values + $group['settings']['display']; fieldgroup_save_group($form['#type_name'], $group); @@ -543,10 +724,51 @@ function fieldgroup_nodeapi(&$node, $op, case 'view': // Prevent against invalid 'nodes' built by broken 3rd party code. if (isset($node->type)) { + //prepare data that will make this easier + $group_rows = array(); + $field_rows = array(); + $groups = fieldgroup_groups($node->type); + if (!empty($groups)) { + foreach ($groups as $name => $more) { + $group_rows[] = $name; + } + } + + $fields = $node->content; + if (!empty($fields)) { + foreach ($fields as $name => $more) { + if (is_string($name) && strstr($name, 'field_')) { + $field_rows[] = $name; + } + } + } + + $max_depth = fieldgroup_order_fields_groups($group_rows, $groups, $field_rows); + + //cover the top level fields that aren't in fieldgroups + if (isset($field_rows)) { + foreach ($field_rows as $name) { + $node->content[$name]['#depth'] = 0; + } + } + // Build the node content element needed to render each fieldgroup. - foreach (fieldgroup_groups($node->type) as $group) { + foreach ($groups as $group) { fieldgroup_build_content($group, $node, $teaser, $page); } + //reorder the groups from the inside-out in order to avoid writing a recursive function + while ($max_depth >= 0) { + foreach ($group_rows as $name) { + if ($node->content[$name]['#depth'] == $max_depth) { + $parent = $node->content[$name]['#group_parent']; + if (isset($parent) && $parent != '') { + $node->content[$parent]['group'][$name] = $node->content[$name]; + unset($node->content[$name]); + } + } + } + $max_depth--; + } } break; } @@ -611,6 +833,8 @@ function fieldgroup_build_content($group foreach ($group['fields'] as $field_name => $field) { if (isset($node->content[$field_name])) { $element[$field_name] = $node->content[$field_name]; + $element[$field_name]['#weight'] = $field['weight']; + $element[$field_name]['#depth'] = $field['depth']; } } @@ -636,10 +860,12 @@ function fieldgroup_build_content($group $wrapper = array( 'group' => $element, '#weight' => $group['weight'], + '#depth' => $group['depth'], '#post_render' => array('fieldgroup_wrapper_post_render'), '#group_name' => $group_name, '#type_name' => $node->type, '#context' => $context, + '#group_parent' => $group['parent_group_name'], ); $node->content[$group_name] = $wrapper; @@ -734,7 +960,7 @@ function fieldgroup_wrapper_post_render( 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. @@ -792,7 +1018,7 @@ function fieldgroup_fields_tablename($ve * @todo * Make this into more of a real API for groups. */ -/* +/** * Saves the given group for this content-type */ function fieldgroup_save_group($type_name, $group) { @@ -803,18 +1029,19 @@ function fieldgroup_save_group($type_nam $function = $module .'_fieldgroup_save_group'; $function($group); } - + 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)", + isset($group['parent']) ? $group['parent'] : $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(), TRUE); 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'", + isset($group['parent']) ? $group['parent'] : $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(), TRUE); return SAVED_UPDATED; } @@ -884,6 +1111,7 @@ function theme_fieldgroup_fieldset($elem 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'] : '';; @@ -910,4 +1138,4 @@ function fieldgroup_preprocess_node(&$va // '#chilren' might not be set if the group is empty. $vars[$group_name .'_rendered'] = isset($node->content[$group_name]['#children']) ? $node->content[$group_name]['#children'] : ''; } -} \ No newline at end of file +} diff -Nurp ../cck.orig/modules/fieldgroup/fieldgroup.module.orig ./modules/fieldgroup/fieldgroup.module.orig --- ../cck.orig/modules/fieldgroup/fieldgroup.module.orig 1969-12-31 18:00:00.000000000 -0600 +++ ./modules/fieldgroup/fieldgroup.module.orig 2010-07-30 11:11:04.000000000 -0500 @@ -0,0 +1,1133 @@ +content. + * - hook_fieldgroup_form: Alter the group portion of the node form. + * - hook_fieldgroup_types: Add additional fieldgroup group_types. + * - hook_fieldgroup_default_settings: Add additional fieldgroup default settings. + * - hook_fieldgroup_save: Do additional processing when a fieldgroup is saved. + */ +/** + * Implementation of hook_init(). + */ +function fieldgroup_init() { + drupal_add_css(drupal_get_path('module', 'fieldgroup') .'/fieldgroup.css'); +} + +/** + * Implementation of hook_ctools_plugin_directory(). + */ +function fieldgroup_ctools_plugin_directory($module, $plugin) { + if ($module == 'ctools' && $plugin == 'content_types') { + return 'panels/' . $plugin; + } +} + +/** + * Implementation of hook_menu(). + */ +function fieldgroup_menu() { + $items = array(); + + // Make sure this doesn't fire until content_types is working, + // needed to avoid errors on initial installation. + if (!defined('MAINTENANCE_MODE')) { + foreach (node_get_types() as $type) { + $type_name = $type->type; + $content_type = content_types($type_name); + $type_url_str = $content_type['url_str']; + $items['admin/content/node-type/'. $type_url_str .'/groups/%'] = array( + 'title' => 'Edit group', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('fieldgroup_group_edit_form', $type_name, 5), + 'access arguments' => array('administer content types'), + 'type' => MENU_CALLBACK, + ); + $items['admin/content/node-type/'. $type_url_str .'/groups/%/remove'] = array( + 'title' => 'Edit group', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('fieldgroup_remove_group', $type_name, 5), + 'access arguments' => array('administer content types'), + 'type' => MENU_CALLBACK, + ); + } + } + return $items; +} + +/** + * Implementation of hook_theme(). + */ +function fieldgroup_theme() { + return array( + 'fieldgroup_simple' => array( + 'template' => 'fieldgroup-simple', + 'arguments' => array('element' => NULL), + ), + 'fieldgroup_fieldset' => array( + 'arguments' => array('element' => NULL), + ), + 'fieldgroup_display_overview_form' => array( + 'arguments' => array('form' => NULL), + ), + ); +} + +/** + * Implementation of hook_elements(). + */ +function fieldgroup_elements() { + return array( + 'fieldgroup_simple' => array(), + 'fieldgroup_fieldset' => array('#collapsible' => FALSE, '#collapsed' => FALSE, '#value' => NULL,), + ); +} + +/** + * Implementation of hook_fieldapi(). + */ +function fieldgroup_content_fieldapi($op, $field) { + switch ($op) { + case 'delete instance': + db_query("DELETE FROM {". fieldgroup_fields_tablename() ."} WHERE field_name = '%s' AND type_name = '%s'", $field['field_name'], $field['type_name']); + cache_clear_all('fieldgroup_data:', content_cache_tablename(), TRUE); + break; + } +} + +function fieldgroup_group_edit_form(&$form_state, $type_name, $group_name) { + $content_type = content_types($type_name); + $groups = fieldgroup_groups($content_type['type']); + + if (!$group = $groups[$group_name]) { + drupal_not_found(); + exit; + } + + $form['label'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#default_value' => $group['label'], + '#required' => TRUE, + ); + + // Set a default value for group type early in the form so it + // can be overridden by subsequent form elements added by other modules. + $group_type = !empty($group['group_type']) ? $group['group_type'] : 'standard'; + $form['group_type'] = array('#type' => 'hidden', '#default_value' => $group_type); + + $form['settings']['#tree'] = TRUE; + $form['settings']['form'] = array( + '#type' => 'fieldset', + '#title' => t('Form settings'), + '#description' => t('These settings apply to the group in the node editing form.'), + ); + $form['settings']['form']['style'] = array( + '#type' => 'radios', + '#title' => t('Style'), + '#default_value' => $group['settings']['form']['style'], + '#options' => array( + 'fieldset' => t('always open'), + 'fieldset_collapsible' => t('collapsible'), + 'fieldset_collapsed' => t('collapsed'), + ) + ); + $form['settings']['form']['description'] = array( + '#type' => 'textarea', + '#title' => t('Help text'), + '#default_value' => $group['settings']['form']['description'], + '#rows' => 5, + '#description' => t('Instructions to present to the user on the editing form.'), + '#required' => FALSE, + ); + $form['settings']['display'] = array( + '#type' => 'fieldset', + '#title' => t('Display settings'), + '#description' => t('These settings apply to the group on node display.'), + ); + $form['settings']['display']['description'] = array( + '#type' => 'textarea', + '#title' => t('Description'), + '#default_value' => $group['settings']['display']['description'], + '#rows' => 5, + '#description' => t('A description of the group.'), + '#required' => FALSE, + ); + + foreach (array_keys(content_build_modes()) as $key) { + $form['settings']['display'][$key]['format'] = array('#type' => 'value', '#value' => isset($group['settings']['display'][$key]['format']) ? $group['settings']['display'][$key]['format'] : 'fieldset'); + $form['settings']['display'][$key]['exclude'] = array('#type' => 'value', '#value' => isset($group['settings']['display'][$key]['exclude']) ? $group['settings']['display'][$key]['exclude'] : 0); + } + $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; + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#weight' => 10, + ); + + return $form; +} + +function fieldgroup_group_edit_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $content_type = $form['#content_type']; + fieldgroup_save_group($content_type['type'], $form_values); + $form_state['redirect'] = 'admin/content/node-type/'. $content_type['url_str'] .'/fields'; +} + +function fieldgroup_remove_group(&$form_state, $type_name, $group_name) { + $content_type = content_types($type_name); + $groups = fieldgroup_groups($content_type['type']); + $group = isset($groups[$group_name]) ? $groups[$group_name] : ''; + + if (empty($group)) { + drupal_not_found(); + exit; + } + + $form['#submit'][] = 'fieldgroup_remove_group_submit'; + $form['#content_type'] = $content_type; + $form['#group_name'] = $group_name; + + return confirm_form($form, + t('Are you sure you want to remove the group %label?', + array('%label' => t($group['label']))), + 'admin/content/node-type/'. $content_type['url_str'] .'/fields', t('This action cannot be undone.'), + t('Remove'), t('Cancel')); +} + +function fieldgroup_remove_group_submit($form, &$form_state) { + $form_values = $form_state['values']; + $content_type = $form['#content_type']; + $group_name = $form['#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'; +} + +/** + * Returns all groups for a content type + */ +function fieldgroup_groups($content_type = '', $sorted = FALSE, $reset = FALSE) { + global $language; + static $groups, $groups_sorted; + if (!isset($groups) || $reset) { + if ($cached = cache_get('fieldgroup_data:'. $language->language, content_cache_tablename())) { + $data = $cached->data; + $groups = $data['groups']; + $groups_sorted = $data['groups_sorted']; + } + else { + $result = db_query("SELECT * FROM {". fieldgroup_tablename() ."} ORDER BY type_name, weight"); + $groups = array(); + $groups_sorted = array(); + while ($group = db_fetch_array($result)) { + $groups[$group['type_name']] = _fieldgroup_get_tree($group['type_name']); + $groups_sorted[$group['type_name']][] = &$groups[$group['type_name']][$group['group_name']]; + } + cache_set('fieldgroup_data', array('groups' => $groups, 'groups_sorted' => $groups_sorted), content_cache_tablename()); + } + } + if (empty($content_type)) { + return $groups; + } + elseif (empty($groups) || empty($groups[$content_type])) { + return array(); + } + 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)) { + $group['settings'] = unserialize($group['settings']); + $group['fields'] = array(); + + // Allow external modules to translate field group strings. + $group_strings = array( + 'label' => $group['label'], + 'form_description' => $group['settings']['form']['description'], + 'display_description' => $group['settings']['display']['description'], + ); + drupal_alter('content_fieldgroup_strings', $group_strings, $type_name, $group['group_name']); + $group['label'] = $group_strings['label']; + $group['settings']['form']['description'] = $group_strings['form_description']; + $group['settings']['display']['description'] = $group_strings['display_description']; + + $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; + } + //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); + + $labels[''] = '<'. t('none') .'>'; + foreach ($groups as $group_name => $group) { + $labels[$group_name] = t($group['label']); + } + return $labels; +} + +function _fieldgroup_field_get_group($content_type, $field_name) { + 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_field_get_parents($content_type, $name, $is_group = FALSE) { + $counter = 0; + if ($is_group) { + $parents[$counter] = $name; + } + else { + if ($result = db_result(db_query("SELECT group_name FROM {". fieldgroup_fields_tablename() ."} WHERE type_name = '%s' AND field_name = '%s'", $content_type, $name))) { + $parents[$counter] = $result; + } + } + while ($result = db_result(db_query("SELECT parent_group_name FROM {". fieldgroup_tablename() ."} WHERE type_name = '%s' AND group_name = '%s'", $content_type, $parents[$counter]))) { + $counter++; + $parents[$counter] = $result; + } + return $parents; +} + +function _fieldgroup_add_group_to_form(&$form, &$form_state, $form_id, $group_name, $group, $groups) { + $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'], + '#depth' => $group['depth'], + '#group_parent' => $group['parent_group_name'], + '#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[$field_name]['#weight'] = $field['weight']; + $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 (!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; + } + else { + //cascade visibility up + $form[$group_name]['#access'] = TRUE; + } + + // 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); + } +} + + +/** + * This is function fieldgroup_order_fields_and_groups + * + * @param array $group_rows An empty array that we will fill. + * @param array $groups All of the info we need about all of the groups for the content type we're working on. + * @param array $field_check_off This contains the fields. We will unset them as we process them. + * + */ +function fieldgroup_order_fields_groups(&$group_rows, &$groups, &$field_check_off) { + $max_depth = 0; + foreach ($group_rows as $name) { + $depth = $groups[$name]['depth']; + if ($depth > $max_depth) { + $max_depth = $depth; + } + $parent = $groups[$name]['parent_group_name']; + + //run through the fields and come up with new weights for display purposes + if (isset($groups[$name]['fields'])) { + foreach ($groups[$name]['fields'] as $name2 => $elements) { + $depth2 = $groups[$name]['depth'] + 1; + $groups[$name]['fields'][$name2]['depth'] = $depth2; + if (in_array($name2, $field_check_off)) { + $index = array_search($name2, $field_check_off); + unset($field_check_off[$index]); + } + } + } + } + return $max_depth; +} + +/** + * 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) { + $group_rows = array(); + $field_rows = array(); + + //prepare data that will make this easier + $groups = fieldgroup_groups($form['type']['#value']); + if (!empty($groups)) { + foreach ($groups as $name => $more) { + $group_rows[] = $name; + } + } + + $fields = $form['#field_info']; + if (!empty($fields)) { + foreach ($fields as $name => $more) { + $field_rows[] = $name; + } + } + + $max_depth = fieldgroup_order_fields_groups($group_rows, $groups, $field_rows); + + //cover the top level fields that aren't in fieldgroups + if (isset($field_rows)) { + foreach ($field_rows as $name) { + $form[$name]['#depth'] = 0; + } + } + + //now that we have the order of things as we want them, let's create the fieldsets for the fieldgroups + foreach ($groups as $group_name => $group) { + _fieldgroup_add_group_to_form($form, $form_state, $form_id, $group_name, $group, $groups); + } + + //reorder the groups from the inside-out in order to avoid a recursive function + while ($max_depth >= 0) { + foreach ($group_rows as $name) { + if ($form[$name]['#depth'] == $max_depth) { + $parent = $form[$name]['#group_parent']; + if (isset($parent) && $parent != '') { + $form[$parent][$name] = $form[$name]; + if ($form[$name]['#access']) { + $form[$parent]['#access'] = TRUE; + } + unset($form[$name]); + $index = array_search($name, $group_rows); + unset($group_rows[$index]); + } + } + } + $max_depth--; + } + + } + // The group is only added here so it will appear in the export + // when using Content Copy. + elseif ($form_id == 'content_field_edit_form' && isset($form['widget'])) { + $content_type = content_types($form['type_name']['#value']); + $form['widget']['group'] = array( + '#type' => 'value', + '#value' => _fieldgroup_field_get_group($content_type['type'], $form['field_name']['#value']), + ); + } + elseif ($form_id == 'content_field_overview_form') { + $form['#validate'][] = 'fieldgroup_field_overview_form_validate'; + $form['#submit'][] = 'fieldgroup_field_overview_form_submit'; + } + elseif ($form_id == 'content_display_overview_form' && !empty($form['#groups'])) { + $form['#submit'][] = 'fieldgroup_display_overview_form_submit'; + if (!isset($form['submit'])) { + $form['submit'] = array('#type' => 'submit', '#value' => t('Save'), '#weight' => 10); + } + } + elseif ($form_id == 'content_field_remove_form') { + $form['#submit'][] = 'fieldgroup_field_remove_form_submit'; + } +} + +/** + * API for group name validation. + * + * Pulled into separate function to be re-usable. + */ +function fieldgroup_validate_name($group, $type_name) { + $errors = array(); + + // No label. + if (!$group['label']) { + $errors['label'][] = t('You need to provide a label.'); + } + + // No group name. + if (!$group['group_name']) { + $errors['group_name'][] = t('You need to provide a group name.'); + } + // Group name validation. + else { + $group_name = $group['group_name']; + $group['group_type'] = !empty($group['group_type']) ? $group['group_type'] : 'standard'; + + // Add the 'group_' prefix. + if (substr($group_name, 0, 6) != 'group_') { + $group_name = 'group_'. $group_name; + } + + // Invalid field name. + if (!preg_match('!^group_[a-z0-9_]+$!', $group_name)) { + $errors['group_name'][] = t('The group name %group_name is invalid. The name must include only lowercase unaccentuated letters, numbers, and underscores.', array('%group_name' => $group_name)); + } + if (strlen($group_name) > 32) { + $errors['group_name'][] = t('The group name %group_name is too long. The name is limited to 32 characters, including the \'group_\' prefix.', array('%group_name' => $group_name)); + } + + // Group name already exists. + $groups = fieldgroup_groups($type_name); + if (isset($groups[$group_name])) { + $errors['group_name'][] = t('The group name %group_name already exists.', array('%group_name' => $group_name)); + } + if (empty($errors['group_name'])) { + $group['group_name'] = $group_name; + } + } + return array('group_name' => $group['group_name'], 'errors' => $errors); +} + +function fieldgroup_field_overview_form_validate($form, &$form_state) { + $form_values = $form_state['values']; + $group = $form_values['_add_new_group']; + + if (array_filter(array($group['label'], $group['group_name']))) { + $validation = fieldgroup_validate_name($group, $form['#type_name']); + if (!empty($validation['errors'])) { + foreach ($validation['errors'] as $type => $messages) { + foreach ($messages as $message) { + if ($type == 'label') { + form_set_error('_add_new_group][label', t('Add new group:') .' '. $message); + } + else { + form_set_error('_add_new_group][group_name', t('Add new group:') .' '. $message); + } + } + } + } + $group_name = $validation['group_name']; + form_set_value($form['_add_new_group']['group_name'], $group_name, $form_state); + } + else { + // Fail validation if attempt to nest fields under a new group without the + // proper information. Not raising an error would cause the nested fields + // to get weights the user doesn't expect. + + foreach ($form_values as $key => $values) { + if ($values['parent'] == '_add_new_group') { + form_set_error('_add_new_group][label', t('Add new group: you need to provide a label.')); + form_set_error('_add_new_group][group_name', t('Add new group: you need to provide a group name.')); + break; + } + } + } +} + +function fieldgroup_field_overview_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $type_name = $form['#type_name']; + + // Create new group if needed. + if (!empty($form_values['_add_new_group']['label'])) { + $group = $form_values['_add_new_group']; + $group['settings'] = field_group_default_settings($group['group_type']); + fieldgroup_save_group($type_name, $group); + $new_group_name = $group['group_name']; + } + + // 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)) { + // If newly added fields were added to a group: + if (in_array($key, $add_field_rows)) { + // We replace the '_add_*_field' key with the actual name of + // the field that got added. + // content_field_overview_form_submit() placed those + // in $form_state['fields_added'] for us. + if (isset($form_state['fields_added'][$key])) { + $key = $form_state['fields_added'][$key]; + } + else { + // No field was actually created : skip to next row. + continue; + } + } + // If the field was added to the newly created group, replace the + // '_add_new_group' value with the actual name of the group. + $parent = ($values['parent'] == '_add_new_group' && isset($new_group_name)) ? $new_group_name : $values['parent']; + // TODO: check the parent group does exist ? + fieldgroup_update_fields(array('field_name' => $key, 'group' => $parent, 'type_name' => $type_name)); + } + } + + 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). + if (in_array($key, $group_rows)) { + $parent = ($values['parent'] == '_add_new_group' && isset($new_group_name)) ? $new_group_name : $values['parent']; + $weight = $values['weight']; + 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(), TRUE); +} + +function field_group_default_settings($group_type) { + $settings = array( + 'form' => array('style' => 'fieldset', 'description' => ''), + 'display' => array('description' => '', 'label' => 'above'), + ); + module_load_include('inc', 'content', 'includes/content.admin'); + foreach (array_keys(content_build_modes()) as $key) { + $settings['display'][$key]['format'] = 'fieldset'; + $settings['display'][$key]['exclude'] = 0; + } + // Allow other modules to add new default settings. + $settings = array_merge($settings, module_invoke_all('fieldgroup_default_settings', $group_type)); + return $settings; +} + +function fieldgroup_display_overview_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + $groups = fieldgroup_groups($form['#type_name']); + foreach ($form_values as $key => $values) { + if (in_array($key, $form['#groups'])) { + $group = $groups[$key]; + // Multigroup data (namely, subgroup data) ends up in the settings array as well + // Prevent this data from ending up inside of ['settings']['display'] + if (is_array($values['settings']) && array_key_exists('multigroup', $values['settings'])){ + if (is_array($group['settings']['multigroup'])){ + foreach ($values['settings']['multigroup'] as $mg_key => $mg_values){ + $group['settings']['multigroup'][$mg_key] = $values['settings']['multigroup'][$mg_key]; + } + } else { + $group['settings']['multigroup'] = $values['settings']['multigroup']; + } + unset($values['settings']['multigroup']); + } + // We have some numeric keys here, so we can't use array_merge. + $group['settings']['display'] = $values + $group['settings']['display']; + fieldgroup_save_group($form['#type_name'], $group); + } + } +} + +function fieldgroup_field_remove_form_submit($form, &$form_state) { + $form_values = $form_state['values']; + // TODO: + // - when a (non last) field is removed from a group, a 'ghost row' remains in the fields overview + // - when the last field is removed, the group disappears + // seems to be fixed when emptying the cache. + db_query("DELETE FROM {". fieldgroup_fields_tablename() ."} WHERE type_name = '%s' AND field_name = '%s'", $form_values['type_name'], $form_values['field_name']); +} + +/** + * Implementation of hook_nodeapi(). + */ +function fieldgroup_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) { + switch ($op) { + case 'view': + // Prevent against invalid 'nodes' built by broken 3rd party code. + if (isset($node->type)) { + //prepare data that will make this easier + $group_rows = array(); + $field_rows = array(); + $groups = fieldgroup_groups($node->type); + if (!empty($groups)) { + foreach ($groups as $name => $more) { + $group_rows[] = $name; + } + } + + $fields = $node->content; + if (!empty($fields)) { + foreach ($fields as $name => $more) { + if (is_string($fields) && strstr($fields, 'field_')) { + $field_rows[] = $name; + } + } + } + + $max_depth = fieldgroup_order_fields_groups($group_rows, $groups, $field_rows); + + //cover the top level fields that aren't in fieldgroups + if (isset($field_rows)) { + foreach ($field_rows as $name) { + $node->content[$name]['#depth'] = 0; + } + } + + // Build the node content element needed to render each fieldgroup. + foreach ($groups as $group) { + fieldgroup_build_content($group, $node, $teaser, $page); + } + //reorder the groups from the inside-out in order to avoid writing a recursive function + while ($max_depth >= 0) { + foreach ($group_rows as $name) { + if ($node->content[$name]['#depth'] == $max_depth) { + $parent = $node->content[$name]['#group_parent']; + if (isset($parent) && $parent != '') { + $node->content[$parent]['group'][$name] = $node->content[$name]; + unset($node->content[$name]); + } + } + } + $max_depth--; + } + } + break; + } +} + +/** + * Build the node content element needed to render a fieldgroup. + * + * @param $group + * The field group definition. + * @param $node + * The node containing the field group to display. Can be a 'pseudo-node', + * containing at least 'type', 'nid', 'vid', and the content for fields + * required for the group. + * @param $teaser + * @param $page + * Similar to hook_nodeapi('view'). + * + * @see fieldgroup_nodeapi() + * @see fieldgroup_view_group() + */ +function fieldgroup_build_content($group, &$node, $teaser, $page) { + // NODE_BUILD_NORMAL is 0, and ('whatever' == 0) is TRUE, so we need a ===. + if ($node->build_mode === NODE_BUILD_NORMAL || $node->build_mode == NODE_BUILD_PREVIEW) { + $context = $teaser ? 'teaser' : 'full'; + } + else { + $context = $node->build_mode; + } + + $group_name = $group['group_name']; + + // Do not include group labels when indexing content. + if ($context == NODE_BUILD_SEARCH_INDEX) { + $group['settings']['display']['label'] = 'hidden'; + } + $label = $group['settings']['display']['label'] == 'above'; + $element = array( + '#title' => $label ? check_plain(t($group['label'])) : '', + '#description' => $label ? content_filter_xss(t($group['settings']['display']['description'])) : '', + ); + $format = isset($group['settings']['display'][$context]['format']) ? $group['settings']['display'][$context]['format'] : 'fieldset'; + + switch ($format) { + case 'simple': + $element['#type'] = 'fieldgroup_simple'; + $element['#group_name'] = $group_name; + $element['#node'] = $node; + break; + case 'hidden': + $element['#access'] = FALSE; + break; + case 'fieldset_collapsed': + $element['#collapsed'] = TRUE; + case 'fieldset_collapsible': + $element['#collapsible'] = TRUE; + case 'fieldset': + $element['#type'] = 'fieldgroup_fieldset'; + $element['#attributes'] = array('class' => 'fieldgroup '. strtr($group['group_name'], '_', '-')); + break; + } + foreach ($group['fields'] as $field_name => $field) { + if (isset($node->content[$field_name])) { + $element[$field_name] = $node->content[$field_name]; + $element[$field_name]['#weight'] = $field['weight']; + $element[$field_name]['#depth'] = $field['depth']; + } + } + + // Allow other modules to alter the group view. + // Can't use module_invoke_all because we want + // to be able to use a reference to $node and $element. + foreach (module_implements('fieldgroup_view') as $module) { + $function = $module .'_fieldgroup_view'; + $function($node, $element, $group, $context); + } + + // Unset the original field values now that we've moved them. + foreach ($group['fields'] as $field_name => $field) { + if (isset($node->content[$field_name])) { + unset($node->content[$field_name]); + } + } + + // The wrapper lets us get the themed output for the group + // to populate the $GROUP_NAME_rendered variable for node templates, + // and hide it from the $content variable if needed. + // See fieldgroup_preprocess_node(), theme_fieldgroup_wrapper(). + $wrapper = array( + 'group' => $element, + '#weight' => $group['weight'], + '#depth' => $group['depth'], + '#post_render' => array('fieldgroup_wrapper_post_render'), + '#group_name' => $group_name, + '#type_name' => $node->type, + '#context' => $context, + '#group_parent' => $group['parent_group_name'], + ); + + $node->content[$group_name] = $wrapper; +} + +/** + * Render a single field group, fully themed with label. + * + * To be used by third-party code (Panels, ...) that needs to output an + * isolated field group. Do *not* use inside node templates, use the + * $GROUP_NAME_rendered variables instead. You can also use the 'simple' + * style format and override the template fieldgroup-simple.tpl.php. + * + * By default, the field group is displayed using the settings defined for the + * 'full node' or 'teaser' contexts (depending on the value of the $teaser param). + * Set $node->build_mode to a different value to use a different context. + * + * Different settings can be specified by adjusting $group['settings']['display']. + * + * @param $group + * The field group definition. + * @param $node + * The node containing the field group to display. Can be a 'pseudo-node', + * containing at least 'type', 'nid', 'vid', and the field data required + * for the group. + * @param $teaser + * @param $page + * Similar to hook_nodeapi('view'). + * @return + * The themed output for the field group. + * + * @see content_view_field() + */ +function fieldgroup_view_group($group, &$node, $teaser = FALSE, $page = FALSE) { + $group_name = $group['group_name']; + $field_types = _content_field_types(); + + // Clone the node to prevent from altering the original. + $node_copy = drupal_clone($node); + + // Use 'full'/'teaser' if not specified otherwise. + $node_copy->build_mode = isset($node_copy->build_mode) ? $node_copy->build_mode : NODE_BUILD_NORMAL; + + // Build the content element for individual fields in the field group. + if (!isset($node_copy->content)) { + $node_copy->content = array(); + } + foreach (array_keys($group['fields']) as $field_name) { + $field = content_fields($field_name, $node_copy->type); + + if (isset($node_copy->{$field_name})) { + $items = $node_copy->{$field_name}; + + // One-field equivalent to _content_field_invoke('sanitize'). + $module = $field_types[$field['type']]['module']; + $function = $module .'_field'; + if (function_exists($function)) { + $function('sanitize', $node_copy, $field, $items, $teaser, $page); + $node_copy->{$field_name} = $items; + } + + $field_view = content_field('view', $node_copy, $field, $items, $teaser, $page); + // content_field('view') adds a wrapper to handle variables and 'excluded' + // fields for node templates. We bypass it and get the actual field. + $node_copy->content[$field_name] = $field_view[$field_name]; + } + } + + // Build the content element of the field group itself. + fieldgroup_build_content($group, $node_copy, $teaser, $page); + + // fieldgroup_build_content() adds a wrapper to handle variables and 'excluded' + // groups for node templates. We bypass it and render the actual field group. + $output = drupal_render($node_copy->content[$group_name]['group']); + + return $output; +} + +/** + * Hide specified fields from the $content variable in node templates. + */ +function fieldgroup_wrapper_post_render($content, $element) { + $groups = fieldgroup_groups($element['#type_name']); + $group = $groups[$element['#group_name']]; + + // The display settings are not in quite the same place in the + // group and the field, so create the value the theme will expect. + $group['display_settings'] = $group['settings']['display']; + if (theme('content_exclude', $content, $group, $element['#context'])) { + return ''; + } + 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. + */ +function fieldgroup_get_group($content_type, $field_name) { + foreach (fieldgroup_groups($content_type) as $group_name => $group) { + if (in_array($field_name, array_keys($group['fields']))) { + return $group_name; + } + } + return FALSE; +} + +/** + * Implementation of hook_node_type() + * React to change in node types + */ +function fieldgroup_node_type($op, $info) { + if ($op == 'update' && !empty($info->old_type) && $info->type != $info->old_type) { + // update the tables + db_query("UPDATE {". fieldgroup_tablename() ."} SET type_name='%s' WHERE type_name='%s'", array($info->type, $info->old_type)); + db_query("UPDATE {". fieldgroup_fields_tablename() ."} SET type_name='%s' WHERE type_name='%s'", array($info->type, $info->old_type)); + cache_clear_all('fieldgroup_data:', content_cache_tablename(), TRUE); + } + elseif ($op == 'delete') { + db_query("DELETE FROM {". fieldgroup_tablename() ."} WHERE type_name = '%s'", $info->type); + db_query("DELETE FROM {". fieldgroup_fields_tablename() ."} WHERE type_name = '%s'", $info->type); + } +} + +function fieldgroup_types() { + $types = array('standard' => t('Standard group')); + // Allow other modules to add new group_types. + $types = array_merge($types, module_invoke_all('fieldgroup_types')); + return $types; +} + +function fieldgroup_tablename($version = NULL) { + if (is_null($version)) { + $version = variable_get('fieldgroup_schema_version', 0); + } + return $version < 6000 ? 'node_group' : 'content_group'; +} + +function fieldgroup_fields_tablename($version = NULL) { + if (is_null($version)) { + $version = variable_get('fieldgroup_schema_version', 0); + } + return $version < 6000 ? 'node_group_fields' : 'content_group_fields'; +} + +/** + * CRUD API for fieldgroup module. + * + * @todo + * Make this into more of a real API for groups. + */ +/** + * Saves the given group for this content-type + */ +function fieldgroup_save_group($type_name, $group) { + $groups = fieldgroup_groups($type_name); + + // Allow other modules to intervene when the group is saved. + foreach (module_implements('fieldgroup_save_group') as $module) { + $function = $module .'_fieldgroup_save_group'; + $function($group); + } + + if (!isset($groups[$group['group_name']])) { + // Accept group name from programmed submissions if valid. + 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)", + isset($group['parent']) ? $group['parent'] : $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(), TRUE); + return SAVED_NEW; + } + else { + 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'", + isset($group['parent']) ? $group['parent'] : $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(), TRUE); + return SAVED_UPDATED; + } +} + +function fieldgroup_update_fields($form_values) { + $default = _fieldgroup_field_get_group($form_values['type_name'], $form_values['field_name']); + + if ($default != $form_values['group']) { + if ($form_values['group'] && !$default) { + db_query("INSERT INTO {". fieldgroup_fields_tablename() ."} (type_name, group_name, field_name) VALUES ('%s', '%s', '%s')", $form_values['type_name'], $form_values['group'], $form_values['field_name']); + } + elseif ($form_values['group']) { + db_query("UPDATE {". fieldgroup_fields_tablename() ."} SET group_name = '%s' WHERE type_name = '%s' AND field_name = '%s'", $form_values['group'], $form_values['type_name'], $form_values['field_name']); + } + else { + db_query("DELETE FROM {". fieldgroup_fields_tablename() ."} WHERE type_name = '%s' AND field_name = '%s'", $form_values['type_name'], $form_values['field_name']); + } + cache_clear_all('fieldgroup_data:', content_cache_tablename(), TRUE); + } +} + +function fieldgroup_delete($content_type, $group_name) { + db_query("DELETE FROM {". fieldgroup_tablename() ."} WHERE type_name = '%s' AND group_name = '%s'", $content_type, $group_name); + db_query("DELETE FROM {". fieldgroup_fields_tablename() ."} WHERE type_name = '%s' AND group_name = '%s'", $content_type, $group_name); + cache_clear_all('fieldgroup_data:', content_cache_tablename(), TRUE); +} + +/** + * Format a fieldgroup using a 'fieldset'. + * + * Derived from core's theme_fieldset, with no output if the content is empty. + */ +function theme_fieldgroup_fieldset($element) { + if (empty($element['#children']) && empty($element['#value'])) { + return ''; + } + + if ($element['#collapsible']) { + drupal_add_js('misc/collapse.js'); + + if (!isset($element['#attributes']['class'])) { + $element['#attributes']['class'] = ''; + } + + $element['#attributes']['class'] .= ' collapsible'; + if ($element['#collapsed']) { + $element['#attributes']['class'] .= ' collapsed'; + } + } + return ''. ($element['#title'] ? ''. $element['#title'] .'' : '') . (isset($element['#description']) && $element['#description'] ? '
'. $element['#description'] .'
' : '') . (!empty($element['#children']) ? $element['#children'] : '') . (isset($element['#value']) ? $element['#value'] : '') ."\n"; +} + + +/** + * Process variables for fieldgroup.tpl.php. + * + * The $variables array contains the following arguments: + * - $group_name + * - $group_name_css + * - $label + * - $description + * - $content + * + * @see fieldgroup-simple.tpl.php + */ +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'] : '';; + $vars['description'] = isset($element['#description']) ? $element['#description'] : '';; + $vars['content'] = isset($element['#children']) ? $element['#children'] : ''; + $vars['template_files'] = array( + 'fieldgroup-simple-', + 'fieldgroup-simple-'. $element['#group_name'], + 'fieldgroup-simple-'. $element['#node']->type, + 'fieldgroup-simple-'. $element['#group_name'] .'-'. $element['#node']->type, + ); +} + +/** + * Theme preprocess function for node. + * + * Adds $GROUP_NAME_rendered variables, + * containing the themed output for the whole group. + */ +function fieldgroup_preprocess_node(&$vars) { + $node = $vars['node']; + + foreach (fieldgroup_groups($node->type) as $group_name => $group) { + // '#chilren' might not be set if the group is empty. + $vars[$group_name .'_rendered'] = isset($node->content[$group_name]['#children']) ? $node->content[$group_name]['#children'] : ''; + } +} diff -Nurp ../cck.orig/modules/fieldgroup/fieldgroup.tabledrag.js ./modules/fieldgroup/fieldgroup.tabledrag.js --- ../cck.orig/modules/fieldgroup/fieldgroup.tabledrag.js 1969-12-31 18:00:00.000000000 -0600 +++ ./modules/fieldgroup/fieldgroup.tabledrag.js 2010-07-30 11:11:04.000000000 -0500 @@ -0,0 +1,249 @@ +// $Id$ + +/** + * @file + * Override Drupal tabledrag methods to add support for nested fieldgroups. + */ + +/** + * Adjust the classes associated with a row's weight when it is dropped. + * Use the parent's name to base the change on. + */ +Drupal.tableDrag.prototype.onDrop = function() { + tableDragObject = this; + var siblingRowUp = $(tableDragObject.rowObject.element).prev('tr'); + var siblingRowsDown = $(tableDragObject.rowObject.element).nextAll('tr'); + var weightField = $('input.field-weight', tableDragObject.rowObject.element); + var siblingWeightFieldDown = $('input.field-weight', siblingRowsDown); + var oldWeightField = $('input.field-weight', tableDragObject.rowObject.element); + var previousRow = $(tableDragObject.rowObject.element).prev('tr'); + + + while (previousRow.length && $('.indentation', previousRow).length >= this.rowObject.indents) { + previousRow = previousRow.prev('tr'); + } + // If we found a row. + if (previousRow.length) { + sourceRow = previousRow[0]; + var oldGroupField = $('input.field-name', sourceRow); + var newGroupName = oldGroupField[0].value.replace(/_/g, '-'); + } + // Otherwise we went all the way to the left of the table without finding + // a parent, meaning this item has been placed at the root level. + else { + var newGroupName = "top"; + } + + var oldGroupName = oldWeightField[0].className.replace(/([^ ]+[ ]+)*field-weight-([^ ]+)([ ]+[^ ]+)*/, '$2'); + var oldClass = 'field-weight-' + oldGroupName; + var newClass = 'field-weight-' + newGroupName; + weightField.removeClass(oldClass).addClass(newClass); + + //Now that we've updated the class names, let's make sure the weight is properly set. + //First make sure that if the rows above are nested deeper, that we find their parent, which is a sibling of + //our current row. + while (siblingRowUp.length && $('.indentation', siblingRowUp).length > this.rowObject.indents) { + siblingRowUp = siblingRowUp.prev('tr'); + } + //before we set the new weight, make sure we know whether the previous row is a sibling or a parent + var siblingIndent = $('.indentation', siblingRowUp).length; + var indents = this.rowObject.indents; + + //if we found a sibling + if (siblingIndent == indents) { + //the previous row is a sibling, so let's set the new weight + var siblingWeightFieldUp = $('input.field-weight', siblingRowUp); + if (siblingWeightFieldUp[0] != undefined){ + var siblingWeightUp = siblingWeightFieldUp[0].value; + var newWeight = parseInt(siblingWeightUp) + 1; + $('input.field-weight', tableDragObject.rowObject.element).val(newWeight); + } + + //now deal with the case where we moved left/right + if (oldClass != newClass) { + $(tableDragObject.rowObject.element).nextAll('tr').each(function() { + if ($('input.field-weight', this).hasClass(newClass)) { + //let's make sure we grab an actual sibling + var prevRow = $(this).prev('tr'); + while (prevRow.length && $('.indentation', prevRow).length > $('.indentation', this).length) { + prevRow = prevRow.prev('tr'); + } + var prevWeight = $('input.field-weight', prevRow).val(); + var incWeight = parseInt(prevWeight) + 1; + $('input.field-weight', this).val(incWeight); + } + }); + } + } + else { + //the previous row is a parent, which means we're at the top of this group, so set the index to zero + $('input.field-weight', tableDragObject.rowObject.element).val(0); + + //find the row's group to pass in to the each function for comparison since we only want to affect the + //weights in this same group + $(tableDragObject.rowObject.element).nextAll('tr').each(function() { + if ($('input.field-weight', this).hasClass(newClass)) { + //let's make sure we grab an actual sibling + var prevRow = $(this).prev('tr'); + while (prevRow.length && $('.indentation', prevRow).length > $('.indentation', this).length) { + prevRow = prevRow.prev('tr'); + } + var prevWeight = $('input.field-weight', prevRow).val(); + var incWeight = parseInt(prevWeight) + 1; + $('input.field-weight', this).val(incWeight); + } + }); + } +} + +/** +* Determine the valid indentations interval for the row at a given position +* in the table. +* +* @param prevRow +* DOM object for the row before the tested position +* (or null for first position in the table). +* @param nextRow +* DOM object for the row after the tested position +* (or null for last position in the table). +*/ +Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function(prevRow, nextRow) { + var minIndent, maxIndent; + var previousRow = $(this.element).prev('tr'); + var previousMultiRow = previousRow; + var thisRow = $(previousRow).next('tr'); + var realNextRow = $(this.element).next('tr'); + var nextMultiRow = realNextRow; + var thisDepth = $('.indentation', thisRow).size(); + var which = 'standard'; + var rowOne, rowTwo, rowThree, rowFour; + + // Minimum indentation: + // Do not orphan the next row. + minIndent = nextRow ? $('.indentation', nextRow).size() : 0; + + if ($(this.element).is('.tabledrag-multigroup') || $(this.element).is('.tabledrag-standardgroup')) { + //find the first multigroup below where we are + while (nextMultiRow.length && (!($(nextMultiRow).is('.tabledrag-multigroup')) || ($('.indentation', nextMultiRow).size() > $('.indentation', thisRow).size()))) { + nextMultiRow = nextMultiRow.next('tr'); + } + if (!($(nextMultiRow).is('.tabledrag-multigroup'))) { + nextMultiRow = null; + } + //find the first standard group below where we are + while (realNextRow.length && !($(realNextRow).is('.tabledrag-standardgroup'))) { + realNextRow = realNextRow.next('tr'); + } + if (!($(realNextRow).is('.tabledrag-standardgroup'))) { + realNextRow = null; + } + + if (typeof (realNextRow) != 'undefined' && realNextRow != undefined) { + rowOne = realNextRow.get(0); + //alert("rowOne " + rowOne.rowIndex); + } + else { + rowOne = null; + } + if (typeof (nextMultiRow) != 'undefined' && nextMultiRow != undefined) { + rowTwo = nextMultiRow.get(0); + //alert("rowTwo " + rowTwo.rowIndex); + } + else { + rowTwo = null; + } + + if (rowOne == null && rowTwo == null) { + realNextRow = nextRow; + } + else if (rowTwo != null && rowOne == null) { + realNextRow = nextMultiRow; + which = 'multi'; + } + else if (rowTwo != null && rowOne != null && rowTwo.rowIndex < rowOne.rowIndex) { + realNextRow = nextMultiRow; + which = 'multi'; + } + + //find the first multigroup above where we are at + while (previousMultiRow.length && !($(previousMultiRow).is('.tabledrag-multigroup'))) { + previousMultiRow = previousMultiRow.prev('tr'); + } + if (!($(previousMultiRow).is('.tabledrag-multigroup'))) { + previousMultiRow = null; + } + //find the first multigroup above where we are at + while (previousRow.length && !($(previousRow).is('.tabledrag-standardgroup'))) { + previousRow = previousRow.prev('tr'); + } + if (!($(previousRow).is('.tabledrag-standardgroup'))) { + previousRow = null; + } + + if (typeof (previousRow) != 'undefined' && previousRow != undefined) { + rowThree = previousRow.get(0); + //alert("rowThree " + rowThree.rowIndex); + } + else { + rowThree = null; + } + if (typeof (previousMultiRow) != 'undefined' && previousMultiRow != undefined) { + rowFour = previousMultiRow.get(0); + //alert("rowFour " + rowFour.rowIndex); + } + else { + rowFour = null; + } + + if (rowThree == null && rowFour == null) { + previousRow = prevRow; + } + else if (rowFour != null && rowThree == null) { + previousRow = previousMultiRow; + which = 'multi'; + } + else if (rowFour != null && rowThree != null && rowFour.rowIndex > rowThree.rowIndex) { + previousRow = previousMultiRow; + which = 'multi'; + } + + if ($(this.element).is('.tabledrag-multigroup') || $(this.element).is('.tabledrag-standardgroup')) { + if (this.direction != 'down') { + if (which == 'standard') { + maxIndent = $('.indentation', previousRow).size() + 1; + } + else { + maxIndent = $('.indentation', previousRow).size(); + } + //alert("up " + maxIndent); + } + else if (this.direction == 'down') { + if (which == 'standard') { + maxIndent = $('.indentation', realNextRow).size() + 1; + } + else { + maxIndent = $('.indentation', realNextRow).size(); + } + //alert("down " + maxIndent); + } + } + } + else { + // Maximum indentation: + if (!prevRow || $(this.element).is('.tabledrag-root')) { + // Do not indent the first row in the table or 'root' rows. + maxIndent = 0; + } + else { + // Do not go deeper than as a child of the previous row. + maxIndent = $('.indentation', prevRow).size() + ($(prevRow).is('.tabledrag-leaf') ? 0 : 1); + // Limit by the maximum allowed depth for the table. + if (this.maxDepth) { + maxIndent = Math.min(maxIndent, this.maxDepth - (this.groupDepth - this.indents)); + } + } + } + //alert("min " + minIndent); + //alert("max " + maxIndent); + return { 'min': minIndent, 'max': maxIndent }; +} diff -Nurp ../cck.orig/theme/theme.inc ./theme/theme.inc --- ../cck.orig/theme/theme.inc 2010-07-30 11:09:25.000000000 -0500 +++ ./theme/theme.inc 2010-07-30 11:11:04.000000000 -0500 @@ -28,6 +28,8 @@ function template_preprocess_content_fie $add_rows[] = $key; } } + $parent_list = array(); + $parent_list['top'] = 'top'; while ($order) { $key = reset($order); $element = &$form[$key]; @@ -48,7 +50,16 @@ function template_preprocess_content_fie $row = new stdClass(); // Add target classes for the tabledrag behavior. - $element['weight']['#attributes']['class'] = 'field-weight'; + if ($element['#row_type'] == 'group') { + $parent_list[$element['group_name']['#value']] = strtr($element['group_name']['#value'], '_', '-'); + } + if (empty($element['parent']['#value']) || !isset($element['parent']['#value'])) { + $element['weight']['#attributes']['class'] = 'field-weight field-weight-' . 'top'; + } + else { + $element['weight']['#attributes']['class'] = 'field-weight field-weight-' . strtr($element['parent']['#value'], '_', '-'); + } + $element['parent']['#attributes']['class'] = 'group-parent'; $element['hidden_name']['#attributes']['class'] = 'field-name'; // Add target classes for the update selects behavior. @@ -74,6 +85,8 @@ function template_preprocess_content_fie $row->class .= isset($element['#add_new']) ? ' content-add-new' : ''; $row->class .= isset($element['#leaf']) ? ' tabledrag-leaf' : ''; $row->class .= isset($element['#root']) ? ' tabledrag-root' : ''; + $row->class .= (isset($element['group_type']['#value']) && $element['group_type']['#value'] == 'Standard group') ? ' tabledrag-standardgroup' : ''; + $row->class .= (isset($element['group_type']['#value']) && $element['group_type']['#value'] == 'Multigroup') ? ' tabledrag-multigroup' : ''; $rows[] = $row; array_shift($order); @@ -82,10 +95,15 @@ function template_preprocess_content_fie $vars['submit'] = drupal_render($form); // Add tabledrag behavior. -// drupal_add_tabledrag('content-field-overview', 'match', 'parent', 'group-parent', 'group-parent', 'field-name', FALSE, 1); - drupal_add_tabledrag('content-field-overview', 'match', 'parent', 'group-parent', 'group-parent', 'field-name', TRUE, 1); -// drupal_add_tabledrag('content-field-overview', 'order', 'sibling', 'field-weight', NULL, NULL, FALSE); - drupal_add_tabledrag('content-field-overview', 'order', 'sibling', 'field-weight'); + //drupal_add_tabledrag('content-field-overview', 'match', 'parent', 'group-parent', 'group-parent', 'field-name', FALSE, 10); + drupal_add_tabledrag('content-field-overview', 'match', 'parent', 'group-parent', 'group-parent', 'field-name', TRUE, 10); + foreach ($parent_list as $name => $parent) { + //drupal_add_tabledrag('content-field-overview', 'order', 'sibling', 'field-weight', 'field-weight-' . $parent, NULL, FALSE); + drupal_add_tabledrag('content-field-overview', 'order', 'sibling', 'field-weight', 'field-weight-' . $parent, NULL, TRUE); + } + + // Override methods in Drupal core tabledrag.js. + drupal_add_js(drupal_get_path('module', 'fieldgroup') .'/fieldgroup.tabledrag.js'); // Add settings for the update selects behavior. $js_fields = array();