delta => value) into * array(delta => field_name => value). * * During validation and submission, the field values are restored to * their normal positions. * * TODO * * Add other methods of setting the way to display combo sub-groups on the * node, for now they are displayed in fieldsets. * * 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. Need to test complex fields to see which ones * work and which don't. */ /** * Implementation of hook_theme(). */ function combofield_theme() { return array( 'combofield_multiple_values' => array( 'arguments' => array('element' => NULL), ), ); } /** * Implementation of hook_fieldgroup_types(). * * @return unknown */ function combofield_fieldgroup_types() { return array('combo' => t('Combined')); } /** * Implementation of hook_form_alter() */ function combofield_form_alter(&$form, $form_state, $form_id) { // If this is a field edit form and the field is in a combo group, // override the multiple value settings. if ($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(); 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'][] = 'combofield_field_overview_form_validate'; } elseif ($form_id == 'fieldgroup_group_edit_form') { return combofield_group_edit_form($form, $form_state, $form_id); } } function combofield_field_overview_form_validate($form, &$form_state) { $form_values = $form_state['values']; $group = $form_values['_add_new_group']; // 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 Combination 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 Combination group.', array('%field' => $field['widget']['label']))); } } } } function combofield_group_edit_form(&$form, &$form_state) { $type_name = $form['#content_type']['name']; $group_name = $form['group_name']['#default_value']; $content_type = content_types($type_name); $groups = fieldgroup_groups($content_type['type']); if (!$group = $groups[$group_name]) { drupal_not_found(); exit; } module_load_include('inc', 'content', 'includes/content.admin'); module_load_include('inc', 'content', 'includes/content.crud'); $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.'), '#weight' => 0, ); $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. '); //$description .= 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 automatically be set to allow this number of values.'); // Maybe we don't need to display the error message since we have validation to prevent this. //$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 repeats'), '#options' => $options, '#default_value' => isset($group['settings']['combo']['multiple']) ? $group['settings']['combo']['multiple'] : '', '#description' => $description, ); $form['#validate'][] = 'combofield_group_edit_form_validate'; return $form; } function combofield_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); if ($max_existing > $form_values['settings']['combo']['multiple']) { form_set_error('settings][combo][multiple', t('The %field in this group already has %multiple values in the database, to prevent the loss of data you cannot set the number of Combined group values to less than this.', array('%field' => $data['label'], '%multiple' => $max_existing))); } } } /** * Implementation of hook_fieldgroup_save_group. * * Additional processing needed when saving a group. */ function combofield_fieldgroup_save_group($group) { if ($group['group_type'] != 'combo') { return; } $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); } } /** * Implementation of hook_fieldgroup_form(). * * 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 combofield_fieldgroup_form(&$form, &$form_state, $form_id, $group) { if ($group['group_type'] != 'combo') { return; } $node = $form['#node']; $fields = $group['fields']; $content_fields = content_fields(); $group_name = $group['group_name']; $max = $group['settings']['combo']['multiple']; $form[$group_name]['#theme'] = 'combofield_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('combofield_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 (!empty($node->$field_name) && count($node->$field_name) >= $delta + 1) { $node_copy->$field_name = array($delta => $node->{$field_name}[$delta]); } else { $node_copy->$field_name = array($delta => NULL); } $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'], 'combofield_combo_item_validate'); } else { $form[$group_name][$delta][$field_name]['#element_validate'] = array('combofield_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 combofield_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); } /** * Implementation of hook_fieldgroup_view(). */ function combofield_fieldgroup_view(&$node, &$element, $group, $context) { if ($group['group_type'] != 'combo') { return; } // TODO Add a method in to set the combo formats. // Just using fieldset for now. $combo_format = isset($group['settings']['combo']['display'][$context]['format']) ? $group['settings']['combo']['display'][$context]['format'] : 'fieldset'; $group_name = $group['group_name']; $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) { // Create a pseudo node that only has the value we want // in this group and pass it to the formatter. 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; } } // TODO Add more ways to format the subgroup displays. switch ($combo_format) { case 'table': case 'simple': case 'fieldset': case 'default': $element[$delta]['#type'] = 'fieldset'; break; } } foreach ($group['fields'] as $field_name => $field) { if (isset($element[$field_name])) { unset($element[$field_name]); } } } /** * 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_combofield_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; }