Index: fieldgroup.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/cck/modules/fieldgroup/fieldgroup.module,v retrieving revision 1.79.2.28 diff -u -r1.79.2.28 fieldgroup.module --- fieldgroup.module 20 Sep 2008 12:26:17 -0000 1.79.2.28 +++ fieldgroup.module 20 Sep 2008 16:57:57 -0000 @@ -1,9 +1,40 @@ delta => value) into + * array(delta => field_name => value). + * + * During validation and submission, the field values are restored to + * their normal positions. + * + * The combo group behaves exactly the same as a normal group + * in node displays, only the node form is different. + * + * TODO + * + * May need to limit this to specific fields that are known to work + * correctly and add validation and warning if other fields are added. + * + * Need to get the AHAH add more button working to create a new delta + * collection of all the group's fields. + * + * Lots of validation. Dragging fields with data into and out of the + * group can cause loss of data since the field's multiple value setting + * will be changed. */ /** * Implementation of hook_init(). @@ -62,6 +93,9 @@ 'fieldgroup_display_overview_form' => array( 'arguments' => array('form' => NULL), ), + 'fieldgroup_multiple_values' => array( + 'arguments' => array('element' => NULL), + ), ); } @@ -102,6 +136,15 @@ '#default_value' => $group['label'], '#required' => TRUE, ); + + $form['group_type'] = array( + '#type' => 'select', + '#title' => t('Type of group'), + '#options' => fieldgroup_types(), + '#default_value' => isset($group['group_type']) ? $group['group_type'] : 'standard', + '#description' => t('Choose whether this is a Standard or Combined group. The fields in a Standard group are independent of each other and each can have either single or multiple values. The fields in a Combined group are treated as a repeating collection of single value fields.'), + ); + $form['settings']['#tree'] = TRUE; $form['settings']['form'] = array( '#type' => 'fieldset', @@ -140,12 +183,29 @@ '#required' => FALSE, ); module_load_include('inc', 'content', 'includes/content.admin'); + module_load_include('inc', 'content', 'includes/content.crud'); foreach (array_merge(array_keys(_content_admin_display_contexts()), array('label')) as $key) { $form['settings']['display'][$key] = array('#type' => 'value', '#value' => $group['settings']['display'][$key]); } + $form['settings']['combo'] = array( + '#type' => 'fieldset', + '#title' => t('Combined group settings'), + '#collapsed' => FALSE, + '#collapsible' => TRUE, + ); + $description = t('Number of times to repeat the collection of Combined group fields. \'Unlimited\' will provide an \'Add more\' button so the users can add repeat it as many times as they like. All fields in this group will be set to allow this number of values.'); + $description .= '

'. t('Warning! You will not be allowed to reduce the number of values for the Combination group after data has been created because it would result in the loss of data!') .'

'; + $form['settings']['combo']['multiple'] = array( + '#tree' => TRUE, + '#type' => 'select', + '#title' => t('Number of combined values'), + '#options' => array('' => t('N/A'), 1 => t('Unlimited'), 0 => 1) + drupal_map_assoc(range(2, 10)), + '#default_value' => isset($group['settings']['combo']['multiple']) ? $group['settings']['combo']['multiple'] : '', + '#description' => $description, + ); + $form['weight'] = array('#type' => 'hidden', '#default_value' => $group['weight']); $form['group_name'] = array('#type' => 'hidden', '#default_value' => $group_name); - $form['group_type'] = array('#type' => 'hidden', '#default_value' => $group['group_type']); $form['#content_type'] = $content_type; $form['submit'] = array( @@ -157,10 +217,29 @@ return $form; } -function fieldgroup_group_edit_form_submit($form, &$form_state) { +function fieldgroup_group_edit_form_validate($form, &$form_state) { $form_values = $form_state['values']; + $group_type = $form_values['group_type']; + if ($group_type != 'combo') { + return; + } + + // Make sure we don't set the multiple values to a number that + // would result in lost data. $content_type = $form['#content_type']; + $groups = fieldgroup_groups($content_type['type']); + $group = $groups[$form_values['group_name']]; + foreach ($group['fields'] as $field_name => $data) { + $max_existing = content_max_delta($field_name, $content_type['type']); + if ($max_existing > $form_values['settings']['combo']['multiple']) { + form_set_error('settings][combo][multiple', t('The content type %type already has %multiple values in the database, to prevent the loss of data you cannot set the number of combo values to less than this.', array('%type' => $content_type['label'], '%multiple' => $max_existing))); + } + } +} +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'; } @@ -287,6 +366,10 @@ // Hide the fieldgroup, because the fields are inaccessible. $form[$group_name]['#access'] = FALSE; } + // If this is a combo group, alter it. + if ($group['group_type'] == 'combo') { + fieldgroup_combo_form($form, $form_state, $form_id, $group); + } } } @@ -294,10 +377,18 @@ // when using Content Copy. elseif ($form_id == 'content_field_edit_form' && isset($form['widget'])) { $content_type = content_types($form['type_name']['#value']); + $groups = fieldgroup_groups($content_type['type']); + $group_name = _fieldgroup_field_get_group($content_type['type'], $form['field_name']['#value']); + $group = isset($groups[$group_name]) ? $groups[$group_name] : array(); $form['widget']['group'] = array( '#type' => 'value', - '#value' => _fieldgroup_field_get_group($content_type['type'], $form['field_name']['#value']), + '#value' => $group_name, ); + // If this field is in a combo group, override the multiple value settings. + if (!empty($group) && $group['group_type'] == 'combo') { + $form['field']['multiple']['#value'] = $group['settings']['combo']['multiple']; + $form['field']['multiple']['#access'] = FALSE; + } } elseif ($form_id == 'content_field_overview_form') { $form['#validate'][] = 'fieldgroup_field_overview_form_validate'; @@ -363,6 +454,7 @@ // 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.')); @@ -371,6 +463,162 @@ } } } + + // See if we have fields moving into or out of a combo group. + $fields = array(); + $groups = array(); + foreach ($form_values as $key => $values) { + if (!empty($form[$key]['#row_type']) && $form[$key]['#row_type'] == 'group') { + // Gather up info about all groups. + $groups[$key] = $form_values[$key]['group']; + } + if (!empty($form[$key]['#row_type']) && $form[$key]['#row_type'] == 'field') { + if ($values['prev_parent'] != $values['parent']) { + // Gather up fields that have moved in or out of a group. + $fields[$key] = $form_values[$key]['field']; + } + } + } + + // TODO Add other validation here to prevent moving fields that + // won't work in combo groups from being added to them. + + if (!empty($fields)) { + foreach ($fields as $field_name => $field) { + $new_group = $form_values[$field_name]['parent']; + $old_group = $form_values[$field_name]['prev_parent']; + if (!empty($new_group) && $groups[$new_group]['group_type'] == 'combo') { + $content_type = content_types($form['#type_name']); + $max_existing = content_max_delta($field_name, $content_type['type']); + if ($max_existing > $groups[$new_group]['settings']['combo']['multiple']) { + form_set_error($field_name, t('The field %field already has %multiple values in the database, to prevent the loss of data you cannot move this field into a Combined group with fewer values.', array('%field' => $field['widget']['label'], '%multiple' => $max_existing))); + } + else { + drupal_set_message(t('You are moving %field into a combo group.', array('%field' => $field['widget']['label']))); + } + } + elseif (!empty($old_group) && $groups[$old_group]['group_type'] == 'combo') { + drupal_set_message(t('You are moving %field out of a combo group.', array('%field' => $field['widget']['label']))); + } + } + } +} + +/** + * Align the delta values of each field in the combo group. + * + * Swap the field name and delta for each combo group so we can + * d-n-d each collection of fields as a single delta item. + */ +function fieldgroup_combo_form(&$form, &$form_state, $form_id, $group) { + //dsm($form); + $node = $form['#node']; + $fields = $group['fields']; + $content_fields = content_fields(); + $group_name = $group['group_name']; + $max = $group['settings']['combo']['multiple']; + + $form[$group_name]['#theme'] = 'fieldgroup_multiple_values'; + $form[$group_name]['#multiple'] = !empty($max); + $form[$group_name]['#group_name'] = $group_name; + $form[$group_name]['#group_label'] = $group['label']; + $form[$group_name]['#element_validate'] = array('fieldgroup_combo_form_validate'); + $form[$group_name]['#tree'] = TRUE; + + for ($delta = 0; $delta < $max; $delta++) { + foreach ($fields as $field_name => $group_field) { + if (empty($form[$group_name][$delta])) { + $form[$group_name] += array($delta => array($field_name => array())); + } + else { + $form[$group_name][$delta][$field_name] = array(); + } + + $field = $content_fields[$field_name]; + if (isset($form[$group_name][$field_name][$delta]['_weight'])) { + $form[$group_name][$delta]['_weight'] = $form[$group_name][$field_name][$delta]['_weight']; + } + + // Make each field into a pseudo single value field + // with the right delta value. + $field['multiple'] = FALSE; + $form['#field_info'][$field_name] = $field; + $node_copy = drupal_clone($node); + + // There may be missing delta values for fields that were never created, so check first. + if (count($node->$field_name) >= $delta + 1) { + $node_copy->$field_name = array($delta => $node->{$field_name}[$delta]); + } + else { + $node_copy->$field_name = array($delta => content_set_empty($field, $node_copy->$field_name)); + } + + // Set the 'node' with the delta value we want do the Content + // module will feed the right $items to the field module. + $form['#node'] = $node_copy; + $field_form = content_field_form($form, $form_state, $field, $delta); + if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) { + $value = array_key_exists($delta, $field_form[$field_name]) ? $delta : 0; + $form[$group_name][$delta][$field_name] = $field_form[$field_name][$value]; + } + else { + $form[$group_name][$delta][$field_name] = $field_form[$field_name]; + } + $form[$group_name][$delta][$field_name]['#weight'] = $field['widget']['weight']; + + // Add in our validation step, and make sure it preceeds other processing + // so we can massage the element back to the normal value. + if (isset($form[$group_name][$delta][$field_name]['#element_validate'])) { + array_unshift($form[$group_name][$delta][$field_name]['#element_validate'], 'fieldgroup_combo_item_validate'); + } + else { + $form[$group_name][$delta][$field_name]['#element_validate'] = array('fieldgroup_combo_item_validate'); + } + } + } + $form['#node'] = $node; + + // Unset the original group values. + foreach ($fields as $field_name => $field) { + unset($form[$group_name][$field_name]); + } + //dsm($form); + $form['#element_validate'][] = 'fieldgroup_combo_form_validate'; +} + +/** + * Swap transposed field/delta values back to their normal positions. + */ +function fieldgroup_combo_item_validate($element, &$form_state) { + //dsm($form_state); + + $field_name = array_pop($element['#parents']); + $delta = array_pop($element['#parents']); + $group = array_pop($element['#parents']); + + //dsm($group.'>'.$field_name.'>'.$delta.' is '.$element['#value']['value']); + + // Examine the post values to see what order the new fields + // belong in. This is very hackish and should be done better, + // but it works for now. + $new = array(); + foreach ($element['#post'][$group] as $count => $value) { + $new[$value['_weight']] = $count; + } + ksort($new); + $count = 0; + foreach ($new as $value) { + if ($delta == $value) { + $delta = $count; + break; + } + $count++; + } + // We figured out what the new order for the fields is, + // so set these values. + array_push($element['#parents'], $field_name); + array_push($element['#parents'], $delta); + form_set_value($element, $element['#value'], $form_state); } function fieldgroup_field_overview_form_submit($form, &$form_state) { @@ -463,7 +711,7 @@ function fieldgroup_nodeapi(&$node, $op, $teaser, $page) { switch ($op) { case 'view': - // NODE_BUILD_NORMAL is 0, and ('whatever' == 0) is TRUE, so we need a ===. + // 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'; } @@ -553,6 +801,10 @@ } } +function fieldgroup_types() { + return array('standard' => t('Standard'), 'combo' => t('Combined')); +} + function fieldgroup_tablename($version = NULL) { if (is_null($version)) { $version = variable_get('fieldgroup_schema_version', 0); @@ -584,15 +836,28 @@ 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']); cache_clear_all('fieldgroup_data', content_cache_tablename()); - return SAVED_NEW; + $ret = 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']); cache_clear_all('fieldgroup_data', content_cache_tablename()); - return SAVED_UPDATED; + $ret = SAVED_UPDATED; + } + + // For a combo group, update all the included fields with the right multiple value setting. + if ($group['group_type']) { + $types = content_types(); + $multiple = $group['settings']['combo']['multiple']; + $result = db_query("SELECT field_name, type_name FROM {". fieldgroup_fields_tablename() ."} WHERE group_name = '%s'", $group['group_name']); + while ($row = db_fetch_array($result)) { + $field = $types[$row['type_name']]['fields'][$row['field_name']]; + $field['multiple'] = $multiple; + content_field_instance_update($field); + } } + return $ret; } function fieldgroup_update_fields($form_values) { @@ -646,6 +911,63 @@ return ''. ($element['#title'] ? ''. $element['#title'] .'' : '') . (isset($element['#description']) && $element['#description'] ? '
'. $element['#description'] .'
' : '') . (!empty($element['#children']) ? $element['#children'] : '') . (isset($element['#value']) ? $element['#value'] : '') ."\n"; } + +/** + * Theme an individual form element. + * + * Combine multiple values into a table with drag-n-drop reordering. + * + * TODO + * With a little tweaking, the original theme, content_multiple_values, + * could be made to work for the fieldgroup, too, and this could be + * eliminated. + */ +function theme_fieldgroup_multiple_values($element) { + $output = ''; + if ($element['#multiple'] >= 1) { + $table_id = $element['#group_name'] .'_values'; + $order_class = $element['#group_name'] .'-delta-order'; + + $header = array( + array( + 'data' => '', + 'colspan' => 2 + ), + t('Order'), + ); + $rows = array(); + + foreach (element_children($element) as $key) { + if ($key !== $element['#group_name'] .'_add_more') { + $element[$key]['_weight']['#attributes']['class'] = $order_class; + $delta_element = drupal_render($element[$key]['_weight']); + $cells = array( + array('data' => '', 'class' => 'content-multiple-drag'), + drupal_render($element[$key]), + array('data' => $delta_element, 'class' => 'delta-order'), + ); + $rows[] = array( + 'data' => $cells, + 'class' => 'draggable', + ); + } + } + + $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'content-multiple-table')); + $output .= $element['#description'] ? '
'. $element['#description'] .'
' : ''; + $output .= drupal_render($element[$element['#group_name'] .'_add_more']); + + drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class); + } + else { + foreach (element_children($element) as $key) { + $output .= drupal_render($element[$key]); + } + } + + return $output; +} + /** * Process variables for fieldgroup.tpl.php. * @@ -681,4 +1003,4 @@ // '#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