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 18:15:45 -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,36 @@ '#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, + ); + + // TODO Get the 'Unlimited' option working, turning it off for now. + $description = t('Number of times to repeat the collection of Combined group fields. '); + //$desecription .= t('\'Unlimited\' will provide an \'Add more\' button so the users can add repeat it as many times as they like. '); + $description .= t('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!') .'
'; + $options = array('' => t('N/A'), 1 => t('Unlimited'), 0 => 1) + drupal_map_assoc(range(2, 10)); + + unset($options[1]); + $form['settings']['combo']['multiple'] = array( + '#tree' => TRUE, + '#type' => 'select', + '#title' => t('Number of combined values'), + '#options' => $options, + '#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 +224,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 +373,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 +384,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 +461,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 +470,167 @@ } } } + + // 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); + + // Set the form '#node' to the delta value we want so the Content + // module will feed the right $items to the field module in + // content_field_form(). + + // 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)); + } + $form['#node'] = $node_copy; + $field_form = content_field_form($form, $form_state, $field, $delta); + + // Place the new $field_form into the $delta position in the group form. + 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'); + } + } + } + // Reset the form '#node' back to its original value. + $form['#node'] = $node; + + // Unset the original group field values now that we've reset them. + foreach ($fields as $field_name => $field) { + unset($form[$group_name][$field_name]); + } + //dsm($form); +} + +/** + * 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 +723,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'; } @@ -501,13 +761,40 @@ break; } - foreach ($group['fields'] as $field_name => $field) { - if (isset($node->content[$field_name])) { - $element[$field_name] = $node->content[$field_name]; - unset($node->content[$field_name]); + if ($group['group_type'] != 'combo') { + foreach ($group['fields'] as $field_name => $field) { + if (isset($node->content[$field_name])) { + $element[$field_name] = $node->content[$field_name]; + unset($node->content[$field_name]); + } } } - + else { + $node_copy = drupal_clone($node); + $max = $group['settings']['combo']['multiple']; + for ($delta = 0; $delta < $max; $delta++) { + $element[$delta] = array('#weight' => $delta); + foreach ($group['fields'] as $field_name => $field) { + if (isset($node->content[$field_name])) { + $node_copy->content[$field_name]['field']['items'] = array($delta => $node->content[$field_name]['field']['items'][$delta]); + $element[$delta][$field_name] = $node_copy->content[$field_name]; + $element[$delta][$field_name]['#delta'] = $delta; + } + } + // TOTO + // This is just to make it easy to see the groups on the node. + // We need to add more formatting options to this. + $element[$delta]['#type'] = 'fieldset'; + $element[$delta]['#title'] = $group['label'] .' '. $delta; + + } + 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 whole group // once the $node->content has been rendered. // See fieldgroup_preprocess_node(). @@ -553,6 +840,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 +875,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 +950,63 @@ return '\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'] ? '