array( 'arguments' => array('element' => NULL), ), 'multigroup_node_label' => array( 'arguments' => array('text' => NULL), ), 'multigroup_display_simple' => array( 'arguments' => array('element' => NULL), ), 'multigroup_display_hr' => array( 'arguments' => array('element' => NULL), ), 'multigroup_display_table' => array( 'arguments' => array('element' => NULL), ), ); } /** * Implementation of hook_fieldgroup_types(). */ function multigroup_fieldgroup_types() { return array('multigroup' => t('Multigroup')); } /** * Implementation of hook_fieldgroup_default_settings(). */ function multigroup_fieldgroup_default_settings($group_type) { if ($group_type == 'multigroup') { module_load_include('inc', 'content', 'includes/content.admin'); $settings = array('multigroup' => array('multiple' => 1)); foreach (array_keys(content_build_modes()) as $key) { $settings['multigroup']['display_settings'][$key]['format'] = 'fieldset'; } return $settings; } } function multigroup_multiple_values() { return array( //'' => t('N/A'), 1 => t('Unlimited'), 0 => 1) + drupal_map_assoc(range(2, 10)); } /** * Implementation of hook_menu(). */ function multigroup_menu() { $items = array(); // Callback for AHAH add more buttons. $items['multigroup/js_add_more'] = array( 'page callback' => 'multigroup_add_more_js', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); return $items; } /** * Implementation of hook_form_alter(). */ function multigroup_form_alter(&$form, $form_state, $form_id) { // If this is a field edit form and the field is in a Multigroup, // 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'] == 'multigroup') { $form['field']['multiple']['#value'] = $group['settings']['multigroup']['multiple']; $form['field']['multiple']['#access'] = FALSE; } } elseif ($form_id == 'content_field_overview_form') { multigroup_field_overview_form($form, $form_state); $form['#validate'][] = 'multigroup_field_overview_form_validate'; } elseif ($form_id == 'content_display_overview_form') { multigroup_display_overview_form($form, $form_state, $form_id); $form['#submit'] = array_merge(array('multigroup_display_overview_form_submit'), $form['#submit']); } elseif ($form_id == 'fieldgroup_group_edit_form') { return multigroup_group_edit_form($form, $form_state, $form_id); } } function multigroup_field_overview_form(&$form, &$form_state) { $options = fieldgroup_types(); $options['standard'] = t('Standard: simple field collection'); $options['multigroup'] = t('Multigroup: fields that move in unison'); $form['_add_new_group']['group_type'] = array( '#type' => 'select', '#description' => t('Type of group.'), '#options' => $options, '#default_value' => 'standard', ); } /** * Validation for creating/moving fields and groups on the * Manage Fields screen. */ function multigroup_field_overview_form_validate($form, &$form_state) { $form_values = $form_state['values']; $type_name = $form['#type_name']; $fields = array(); $groups = array(); $group = $form_values['_add_new_group']; if (array_filter(array($group['label'], $group['group_name']))) { $group['settings'] = field_group_default_settings($form_values['_add_new_group']['group_type']); $group = $form_values['_add_new_group']; $validation = fieldgroup_validate_name($group); // If there's something wrong with the new group, // don't bother doing any more validation, further // processing will be stopped by the fieldgroup module. if (!empty($validation['errors'])) { return; } $group['group_name'] = $validation['group_name']; $new_group_name = $group['group_name']; $groups['_add_new_group'] = $group; } // See if we have fields moving into or out of a Multigroup. // Set any fields to use the new name here so they will get processed // correctly by the fieldgroup module when saved. foreach ($form_values as $key => $values) { if ($values['parent'] == '_add_new_group') { $values['parent'] = $new_group_name; $form_values[$key] = $values; } if (!empty($form[$key]['#row_type']) && $form[$key]['#row_type'] == 'group') { // Gather up info about all groups. $group_name = $form_values[$key]['group']['group_name']; $groups[$group_name] = $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']; } } } 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) && isset($groups[$new_group]) && $groups[$new_group]['group_type'] == 'multigroup') { $allowed_in = multigroup_allowed_in($field, $groups[$new_group]); if (!$allowed_in['allowed']) { form_set_error($field_name, $allowed_in['message']); } else { if (!empty($allowed_in['message'])) { drupal_set_message($allowed_in['message']); } module_load_include('inc', 'content', 'includes/content.crud'); $content_type = content_types($type_name); $multiple = $groups[$new_group]['settings']['multigroup']['multiple']; $field = $content_type['fields'][$field_name]; $field['multiple'] = $multiple; $field = content_field_instance_collapse($field); content_field_instance_update($field); drupal_set_message(t('The field %field has been updated to use %multiple values, to match the multiple value setting of the Multigroup %group.', array( '%field' => $field['label'], '%multiple' => $multiple, '%group' => $groups[$new_group]['label']))); } } elseif (!empty($old_group) && isset($groups[$old_group]) && $groups[$old_group]['group_type'] == 'multigroup') { $allowed_out = multigroup_allowed_out($field, $groups[$old_group]); if (!$allowed_out['allowed']) { form_set_error($field_name, $allowed_out['message']); } elseif (!empty($allowed_out['message'])) { drupal_set_message($allowed_out['message']); } } } } } /** * Helper function for deciding if a field is * allowed into a Multigroup. */ function multigroup_allowed_in($field, $group) { if ($group['group_type'] != 'multigroup') { return array('allowed' => TRUE, 'message' => ''); } // We can't allow fields with more multiple values than the group has // to be moved into it. $max_existing = content_max_delta($field['field_name']); $group_max = $group['settings']['multigroup']['multiple']; if ($max_existing > $group_max) { return array( 'allowed' => FALSE, 'message' => t('This change is not allowed. The field %field already has %multiple values in the database but the group %group only allows %group_max. Making this change would result in the loss of data.', array('%field' => $field['widget']['label'], '%multiple' => $max_existing, '%group' => $group['label'], '%group_max' => $group_max)) ); } // Fields that handle their own multiple values may not have the same values // in Multigroup fields and normal fields. We don't know if they will work or not. // Adding a hook here where widgets that handle their own multiple values // that will work correctly in Multigroups can allow their fields in. if (content_handle('widget', 'multiple values', $field) != CONTENT_HANDLE_CORE) { $allowed_widgets = array('optionwidgets_select', 'optionwidgets_buttons', 'optionwidgets_onoff'); $allowed_widgets = array_merge($allowed_widgets, module_invoke_all('multigroup_allowed_widgets')); if (!in_array($field['widget']['type'], $allowed_widgets)) { return array( 'allowed' => FALSE, 'message' => t('This change is not allowed. The field %field handles multiple values differently than the Content module. Making this change could result in the loss of data.', array('%field' => $field['widget']['label'])) ); } } // Allow other modules to intervene. // Any failure will prevent this action. foreach (module_implements('multigroup_allowed_in') as $module) { $function = $module .'_multigroup_allowed_in'; $result = $function($field, $group); if ($result['allowed'] === FALSE) { return array('allowed' => FALSE, 'message' => $result['message']); } } $message = t('You are moving the field %field into a Multigroup.', array('%field' => $field['widget']['label'])); return array('allowed' => TRUE, 'message' => $message); } /** * Helper function for deciding if a field is * allowed out of a Multigroup. */ function multigroup_allowed_out($field, $group) { if ($group['group_type'] != 'multigroup') { return array('allowed' => TRUE, 'message' => ''); } // Optionwidgets do not behave the same in a Multigroup field as out of it. // In a Multigroup the same option can be selected multiple times, // but that is not possible in a normal group. // Adding a hook here where widgets that handle their own multiple values // can indicate their fields should not be removed from Multigroups. $max_existing = content_max_delta($field['field_name']); $no_remove_widgets = array('optionwidgets_select', 'optionwidgets_buttons', 'optionwidgets_onoff'); $no_remove_widgets = array_merge($no_remove_widgets, module_invoke_all('multigroup_no_remove_widgets')); if (in_array($field['widget']['type'], $no_remove_widgets) && $max_existing > 0) { return array( 'allowed' => FALSE, 'message' => t('This change is not allowed. The field %field already has data created and uses a widget that stores data differently in a Standard group than in a Multigroup. Making this change could result in the loss of data.', array('%field' => $field['widget']['label'])) ); } // Allow other modules to intervene. // Any failure will prevent this action. foreach (module_implements('multigroup_allowed_out') as $module) { $function = $module .'_multigroup_allowed_out'; $result = $function($field, $group); if ($result['allowed'] === FALSE) { return array('allowed' => FALSE, 'message' => $result['message']); } } $message = t('You are moving the field %field out of a Multigroup.', array('%field' => $field['widget']['label'])); return array('allowed' => TRUE, 'message' => $message); } /** * Menu callback; presents a listing of fields display settings for a content type. * * Add an additional selector for setting multigroup field display format. */ function multigroup_display_overview_form(&$form, &$form_state) { $type_name = $form['#type_name']; $contexts_selector = $form['#contexts']; // Gather type information. $type = content_types($type_name); $field_types = _content_field_types(); $fields = $type['fields']; $groups = $group_options = array(); if (module_exists('fieldgroup')) { $groups = fieldgroup_groups($type['type']); $group_options = _fieldgroup_groups_label($type['type']); } $contexts = content_build_modes($contexts_selector); // Multigroups, extra values. $label_options = array( 'above' => t('Above'), 'hidden' => t(''), ); $options = array( 'none' => t('none'), 'fieldset' => t('fieldset'), 'hr' => t('horizontal line'), //'table' => t('table'), // TODO add this later 'hidden' => t(''), ); foreach ($groups as $name => $group) { if ($group['group_type'] != 'multigroup') { continue; } $defaults = $group['settings']['multigroup']['display_settings']; $form_name = $name .'_subgroup'; $form['#fields'] = array_merge(array($form_name), $form['#fields']); $form[$form_name] = array( 'human_name' => array('#value' => t('[Subgroup format]')), 'weight' => array('#type' => 'value', '#value' => -20), 'parent' => array('#type' => 'value', '#value' => $name), ); if ($contexts_selector == 'basic') { $form[$form_name]['label'] = array( '#type' => 'select', '#options' => $label_options, '#default_value' => isset($defaults['label']) ? $defaults['label'] : 'above', ); } foreach ($contexts as $key => $title) { $form[$form_name][$key]['format'] = array( '#type' => 'select', '#options' => $options, '#default_value' => isset($defaults[$key]) ? $defaults[$key] : 'fieldset', ); } } return $form; } /** * Submit handler for the display overview form. * * Do this in pre_save so we catch it before the content module * tries to use our 'field'. */ function multigroup_display_overview_form_submit($form, &$form_state) { $form_values = $form_state['values']; // Find any groups we inserted into the display fields form, // save our settings, and remove them from $form_state. foreach ($form_values as $key => $values) { if (in_array($key, $form['#fields']) && substr($key, -9) == '_subgroup') { $group_name = str_replace('_subgroup', '', $key); $groups = fieldgroup_groups($form['#type_name']); $group = $groups[$group_name]; // We have some numeric keys here, so we can't use array_merge. foreach ($values as $k => $v) { $group['settings']['multigroup']['display_settings'][$k] = $v; } fieldgroup_save_group($form['#type_name'], $group); // Make sure group information is immediately updated. cache_clear_all('fieldgroup_data', content_cache_tablename()); fieldgroup_groups('', FALSE, TRUE); unset($form_state['values'][$key]); } } } /** * Alter the Fieldgroup edit form * to add Multigroup settings. */ function multigroup_group_edit_form(&$form, &$form_state) { $type_name = $form['#content_type']['type']; $group_name = $form['group_name']['#default_value']; $content_type = content_types($type_name); $groups = fieldgroup_groups($content_type['type']); $group = $groups[$group_name]; if ($group['group_type'] != 'multigroup') { return; } module_load_include('inc', 'content', 'includes/content.admin'); module_load_include('inc', 'content', 'includes/content.crud'); $form['group_type'] = array( '#type' => 'hidden', '#value' => $group['group_type'], ); $form['settings']['multigroup'] = array( '#type' => 'fieldset', '#title' => t('Other settings'), '#collapsed' => FALSE, '#collapsible' => TRUE, ); $description = t('Number of times to repeat the collection of Multigroup 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.'); $multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1; $form['settings']['multigroup']['multiple'] = array( '#tree' => TRUE, '#type' => 'select', '#title' => t('Number of repeats'), '#options' => multigroup_multiple_values(), '#default_value' => $multiple, '#description' => $description, ); $form['settings']['multigroup']['labels'] = array( '#type' => 'fieldset', '#title' => t('Labels'), '#description' => t("Labels for each subgroup of fields. Labels can be hidden or shown in various contexts using the 'Display fields' screen."), ); if ($multiple < 2) { $multiple = 0; } for ($i = 0; $i < 10; $i++) { $form['settings']['multigroup']['labels'][$i] = array( '#type' => 'textfield', '#title' => t('Subgroup %number label', array('%number' => $i + 1)), '#default_value' => isset($group['settings']['multigroup']['labels'][$i]) ? $group['settings']['multigroup']['labels'][$i] : '', ); } $form['#validate'][] = 'multigroup_group_edit_form_validate'; $form['#submit'][] = 'multigroup_group_edit_form_submit'; return $form; } /** * Validate the Fieldgroup edit form. */ function multigroup_group_edit_form_validate($form, &$form_state) { $form_values = $form_state['values']; $group_type = $form_values['group_type']; if ($group_type != 'multigroup') { return; } $content_type = $form['#content_type']; $groups = fieldgroup_groups($content_type['type']); $group = $groups[$form_values['group_name']]; foreach ($group['fields'] as $field_name => $data) { // Make sure we don't set the multiple values to a number that // would result in lost data. $max_existing = content_max_delta($field_name); if ($form_values['settings']['multigroup']['multiple'] != 1 && $max_existing > $form_values['settings']['multigroup']['multiple']) { form_set_error('settings][multigroup][multiple', t('The field %field in this group already has %multiple values in the database. To prevent the loss of data you cannot set the number of Multigroup values to less than this.', array('%field' => $data['label'], '%multiple' => $max_existing))); } } } /** * Submit the Fieldgroup edit form. * * Update multiple values of fields contained in Multigroups. */ function multigroup_group_edit_form_submit($form, &$form_state) { $form_values = $form_state['values']; $group_type = $form_values['group_type']; if ($group_type != 'multigroup') { return; } module_load_include('inc', 'content', 'includes/content.crud'); $content_type = $form['#content_type']; $groups = fieldgroup_groups($content_type['type']); $group = $groups[$form_values['group_name']]; $multiple = $form_values['settings']['multigroup']['multiple']; foreach ($group['fields'] as $field_name => $data) { $field = $content_type['fields'][$field_name]; $field['multiple'] = $multiple; $field = content_field_instance_collapse($field); content_field_instance_update($field); } } /** * Implementation of hook_fieldgroup_form(). * * Align the delta values of each field in the Multigroup. * * Swap the field name and delta for each Multigroup so we can * d-n-d each collection of fields as a single delta item. */ function multigroup_fieldgroup_form(&$form, &$form_state, $form_id, $group) { if ($group['group_type'] != 'multigroup' || !empty($form[$group['group_name']]['#access']) || empty($form[$group['group_name']])) { return; } $node = $form['#node']; $fields = $group['fields']; $content_fields = content_fields(); $group_name = $group['group_name']; // Use the first field in the group to get the item counts. $first_field_name = array_shift(array_keys($group['fields'])); $first_field = isset($content_fields[$first_field_name]) ? $content_fields[$first_field_name] : array(); $first_field_items = isset($node->$first_field_name) ? $node->$first_field_name : array(); $group['multiple'] = $group['settings']['multigroup']['multiple']; switch ($group['multiple']) { case 0: $max = 0; break; case 1: $filled_items = content_set_empty($first_field, $first_field_items); $current_item_count = isset($form_state['item_count'][$group_name]) ? $form_state['item_count'][$group_name] : count($first_field_items); // We always want at least one empty icon for the user to fill in. $max = ($current_item_count > count($filled_items)) ? $current_item_count - 1 : $current_item_count; break; default: $max = $group['multiple'] - 1; break; } $form[$group_name]['#theme'] = 'multigroup_node_form'; $form[$group_name]['#multiple'] = !empty($max); $form[$group_name]['#type_name'] = $group['type_name']; $form[$group_name]['#group_name'] = $group_name; $form[$group_name]['#group_label'] = $group['label']; $form[$group_name]['#element_validate'] = array('multigroup_node_form_validate'); $form[$group_name]['#tree'] = TRUE; for ($delta = 0; $delta <= $max; $delta++) { multigroup_group_form($form, $form_state, $group, $delta); } // Unset the original group field values now that we've moved them. foreach ($fields as $field_name => $field) { unset($form[$group_name][$field_name]); } if ($add_more = multigroup_add_more($form, $form_state, $group)) { $form[$group_name] += $add_more; } } /** * Create a new delta value for the group. * * Called in form_alter and by AHAH add more. */ function multigroup_group_form(&$form, &$form_state, $group, $delta) { if ($group['group_type'] != 'multigroup' || !empty($form[$group['group_name']]['#access']) || empty($form[$group['group_name']])) { return; } module_load_include('inc', 'content', 'includes/content.node_form'); $node = $form['#node']; $fields = $group['fields']; $content_fields = content_fields(); $group_name = $group['group_name']; $group['multiple'] = $group['settings']['multigroup']['multiple']; $form[$group_name]['#fields'] = array_keys($group['fields']); 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(); } $form[$group_name][$delta]['_weight'] = array( '#type' => 'weight', '#delta' => $delta, // this 'delta' is the 'weight' element's property '#default_value' => $delta, '#weight' => 100, ); $form[$group_name][$delta]['_delta'] = array( '#type' => 'hidden', '#value' => $delta, ); $field = $content_fields[$field_name]; // 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 position. if (empty($form[$group_name][$delta][$field_name]['#element_validate'])) { $form[$group_name][$delta][$field_name]['#element_validate'] = array(); } array_unshift($form[$group_name][$delta][$field_name]['#element_validate'], 'multigroup_node_item_validate'); } // Reset the form '#node' back to its original value. $form['#node'] = $node; return $form; } /** * Swap transposed field/delta values back * to their normal positions in the node. */ function multigroup_node_item_validate($element, &$form_state) { static $weights = array(); //dsm($form_state['values']); $form_values = $form_state['values']; $field_name = array_pop($element['#parents']); $delta = array_pop($element['#parents']); $group_name = array_pop($element['#parents']); // Identify the new delta value for each field. // Find the original delta values for this group, save as static value // because the group will acquire and lose values while we process it. if (!array_key_exists($group_name, $weights)) { $items = $form_state['values'][$group_name]; $weights[$group_name] = array(); foreach ($items as $count => $value) { // Allow for the possibility of matching _weights and missing deltas. $weight = floatval($value['_weight']); $old_delta = intval($value['_delta']); if (empty($weights[$group_name][$weight]) || !in_array($old_delta, $weights[$group_name][$weight])) { $weights[$group_name][$weight][] = $old_delta; } } ksort($weights[$group_name]); } $count = 0; foreach ($weights[$group_name] as $weight => $values) { foreach ($values as $old_delta) { if ($old_delta === $delta) { $delta = $count; //dsm('moving delta values: '.$group_name.'>'.$field_name.'>'.'from '. $old_delta .' to '. $delta); break 2; } $count++; } } // We figured out what the new order for the fields is, // so set the value for the new delta. // We move these new values back up to the top level of the // node and out of the group so the Content module will find and // save the new values and so they don't get mixed into the // remaining, unaltered, values in the group. array_push($element['#parents'], $field_name); array_push($element['#parents'], $delta); // It's very important to use $form_values instead of $element['#value'] // here, because $element['#value'] is sometimes missing changes // made in #element_validate processing done by other modules. $value = isset($form_values[$group_name][$delta][$field_name]) ? $form_values[$group_name][$delta][$field_name] : NULL; // Fields that use optionwidgets have an extra array level in the value // because of the optionwidgets transposition that forces a delta value // into the result array. This works fine when a delta value is between // the field name and the field value, as in normal nodes, but not when // we reverse the field and the delta, so in this case we need to // promote the nested delta value back up to the field level. if (is_array($value) && multigroup_uses_optionwidgets($field_name, $element['#type_name'])) { $value = array_shift($value); } //dsm('setting value of '. $field_name.'>'.$delta); //dsm($value); form_set_value($element, $value, $form_state); } /** * Helper function for identifying fields that use * optionwidgets transpositions. */ function multigroup_uses_optionwidgets($field_name, $type_name) { //static $optionwidgets; if (empty($optionwidgets)) { $optionwidgets = array( 'optionwidgets_select', 'optionwidgets_buttons', 'optionwidgets_onoff', //'nodereference_autocomplete', 'nodereference_buttons', 'nodereference_select', //'userreference_autocomplete', 'userreference_buttons', 'userreference_select', ); // Add hook where other widgets that use optionwidgets can announce it. $optionwidgets = array_merge($optionwidgets, module_invoke_all('multigroup_uses_optionwidgets')); } $types = content_types($type_name); $fields = $types['fields']; $field = $fields[$field_name]; if (in_array($field['widget']['type'], $optionwidgets)) { return TRUE; } return FALSE; } /** * Validation for the whole node group. */ function multigroup_node_form_validate($element, $form_state) { // We moved all the new field values out of the field group // and up to the top level of the node, now get rid of the // original group values. form_set_value($element, NULL, $form_state); return; } /** * Implementation of hook_fieldgroup_view(). */ function multigroup_fieldgroup_view(&$node, &$element, $group, $context) { if ($group['group_type'] != 'multigroup') { return; } $group_name = $group['group_name']; $node_copy = drupal_clone($node); $max = $group['settings']['multigroup']['multiple']; $count = 0; foreach ($group['fields'] as $field_name => $field) { $count = max($count, count($node->$field_name)); } $group['multiple'] = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1; $labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array(); $format = isset($group['settings']['multigroup']['display_settings'][$context]['format']) ? $group['settings']['multigroup']['display_settings'][$context]['format'] : 'fieldset'; $show_label = isset($group['settings']['multigroup']['display_settings']['label']) ? $group['settings']['multigroup']['display_settings']['label'] : 'above'; switch ($group['multiple']) { case 0: $max = 0; break; case 1: $max = $count; break; default: $max = $group['multiple']; break; } for ($delta = 0; $delta < $max; $delta++) { $element[$delta] = array('#weight' => $delta); $label = !empty($labels[$delta]) && $show_label == 'above' ? $labels[$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 => isset($node->content[$field_name]['field']['items'][$delta]) ? $node->content[$field_name]['field']['items'][$delta] : NULL, ); $element[$delta][$field_name] = $node_copy->content[$field_name]; $element[$delta][$field_name]['#delta'] = $delta; } } switch ($format) { case 'table': $element[$delta]['#theme'] = 'multigroup_display_table'; $element[$delta]['#title'] = $label; break; case 'fieldset': $element[$delta]['#type'] = 'fieldset'; $element[$delta]['#title'] = $label; break; case 'hr': $element[$delta]['#theme'] = 'multigroup_display_hr'; $element[$delta]['#title'] = $label; break; default: $element[$delta]['#theme'] = 'multigroup_display_simple'; $element[$delta]['#title'] = $label; 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. */ function theme_multigroup_node_form($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(); $groups = fieldgroup_groups($element['#type_name']); $group = $groups[$element['#group_name']]; $labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array(); $multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1; $i = 0; foreach (element_children($element) as $delta => $key) { if ($key !== $element['#group_name'] .'_add_more') { $label = !empty($labels[$i]) ? theme('multigroup_node_label', $labels[$i]) : ''; $element[$key]['_weight']['#attributes']['class'] = $order_class; $delta_element = drupal_render($element[$key]['_weight']); $cells = array( array('data' => '', 'class' => 'content-multiple-drag'), $label . drupal_render($element[$key]), array('data' => $delta_element, 'class' => 'delta-order'), ); $rows[] = array( 'data' => $cells, // TODO Tablesort drag n drop is not working with complex // field validation. The fields appear to work correctly, // but element validation seems to get missed or confused // causing validation errors. Need to investigate why. //'class' => 'draggable', ); } $i++; } $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; } function multigroup_add_more(&$form, &$form_state, $group) { // Add AHAH add more button, if not working with a programmed form. $multiple = $group['settings']['multigroup']['multiple']; $form_element = array(); if ($multiple != 1 || !empty($form['#programmed'])) { return $form_element; } else { // Make sure the form is cached so ahah can work. $form['#cache'] = TRUE; $content_type = content_types($group['type_name']); $group_name = $group['group_name']; $group_name_css = str_replace('_', '-', $group_name); $form_element[$group_name .'_add_more'] = array( '#type' => 'submit', '#name' => $group_name .'_add_more', '#value' => t('Add more values'), '#weight' => $multiple + 1, // Submit callback for disabled JavaScript. drupal_get_form() might get // the form from the cache, so we can't rely on content_form_alter() // including this file. Therefore, call a proxy function to do this. '#submit' => array('multigroup_add_more_submit_proxy'), '#ahah' => array( 'path' => 'multigroup/js_add_more/'. $content_type['url_str'] .'/'. $group_name, 'wrapper' => $group_name_css .'-items', 'method' => 'replace', 'effect' => 'fade', ), // When JS is disabled, the content_add_more_submit handler will find // the relevant field using these entries. '#group_name' => $group_name, '#type_name' => $group['type_name'], ); // Add wrappers for the group and 'more' button. // TODO: could be simplified ? $form_element['#prefix'] = '
'; $form_element[$group_name .'_add_more']['#prefix'] = '
'; $form_element[$group_name .'_add_more']['#suffix'] = '
'; } return $form_element; } /** * Submit handler to add more choices to a content form. This handler is used when * JavaScript is not available. It makes changes to the form state and the * entire form is rebuilt during the page reload. */ function multigroup_add_more_submit($form, &$form_state) { // Set the form to rebuild and run submit handlers. node_form_submit_build_node($form, $form_state); $group_name = $form_state['clicked_button']['#group_name']; $type_name = $form_state['clicked_button']['#type_name']; // Make the changes we want to the form state. if ($form_state['values'][$group_name][$group_name .'_add_more']) { $form_state['item_count'][$group_name] = count($form_state['values'][$group_name]); } } /** * Menu callback for AHAH addition of new empty widgets. * * Adapted from content_add_more_js to work with groups instead of fields. */ function multigroup_add_more_js($type_name_url, $group_name) { $type = content_types($type_name_url); $groups = fieldgroup_groups($type['type']); $group = $groups[$group_name]; $group['multiple'] = $group['settings']['multigroup']['multiple']; if (($group['multiple'] != 1) || empty($_POST['form_build_id'])) { // Invalid request. drupal_json(array('data' => '')); exit; } // Retrieve the cached form. $form_state = array('submitted' => FALSE); $form_build_id = $_POST['form_build_id']; $form = form_get_cache($form_build_id, $form_state); if (!$form) { // Invalid form_build_id. drupal_json(array('data' => '')); exit; } // We don't simply return a new empty widget to append to existing ones, because // - ahah.js won't simply let us add a new row to a table // - attaching the 'draggable' behavior won't be easy // So we resort to rebuilding the whole table of widgets including the existing ones, // which makes us jump through a few hoops. // The form that we get from the cache is unbuilt. We need to build it so that // _value callbacks can be executed and $form_state['values'] populated. // We only want to affect $form_state['values'], not the $form itself // (built forms aren't supposed to enter the cache) nor the rest of $form_data, // so we use copies of $form and $form_data. $form_copy = $form; $form_state_copy = $form_state; $form_copy['#post'] = array(); form_builder($_POST['form_id'], $form_copy, $form_state_copy); // Just grab the data we need. $form_state['values'] = $form_state_copy['values']; // Reset cached ids, so that they don't affect the actual form we output. form_clean_id(NULL, TRUE); // Sort the $form_state['values'] we just built *and* the incoming $_POST data // according to d-n-d reordering. unset($form_state['values'][$group_name][$group['group_name'] .'_add_more']); foreach ($_POST[$group_name] as $delta => $item) { $form_state['values'][$group_name][$delta]['_weight'] = $item['_weight']; $form_state['values'][$group_name][$delta]['_delta'] = $item['_delta']; } $form_state['values'][$group_name] = _content_sort_items($group, $form_state['values'][$group_name]); $_POST[$group_name] = _content_sort_items($group, $_POST[$group_name]); // Build our new form element for the whole group, asking for one more element. $form_state['item_count'] = array($group_name => count($_POST[$group_name]) + 1); $delta = max(array_keys($_POST[$group_name])) + 1; multigroup_group_form($form, $form_state, $group, $delta); // Save the new definition of the form. $form_state['values'] = array(); form_set_cache($form_build_id, $form, $form_state); // Build the new form against the incoming $_POST values so that we can // render the new element. $_POST[$group_name][$delta]['_weight'] = $delta; $form_state = array('submitted' => FALSE); $form += array( '#post' => $_POST, '#programmed' => FALSE, ); $form = form_builder($_POST['form_id'], $form, $form_state); // Render the new output. $group_form = $form[$group_name]; // We add a div around the new content to receive the ahah effect. $group_form[$delta]['#prefix'] = '
'. (isset($group_form[$delta]['#prefix']) ? $group_form[$delta]['#prefix'] : ''); $group_form[$delta]['#suffix'] = (isset($group_form[$delta]['#suffix']) ? $group_form[$delta]['#suffix'] : '') .'
'; // If a newly inserted widget contains AHAH behaviors, they normally won't // work because AHAH doesn't know about those - it just attaches to the exact // form elements that were initially specified in the Drupal.settings object. // The new ones didn't exist then, so we need to update Drupal.settings // by ourselves in order to let AHAH know about those new form elements. $javascript = drupal_add_js(NULL, NULL); $output_js = isset($javascript['setting']) ? '' : ''; $output = theme('status_messages') . drupal_render($group_form) . $output_js; drupal_json(array('status' => TRUE, 'data' => $output)); exit; } /** * Theme the sub group label in the node form. */ function theme_multigroup_node_label($text) { if (!empty($text)) { return '

'. check_plain($text) .'

'; } } function theme_multigroup_display_simple($element) { $label = ''; if (!empty($element['#title'])) { $label .= ''; } $output = $label; foreach (element_children($element) as $key) { $output .= drupal_render($element[$key]); } return $output; } function theme_multigroup_display_hr($element) { $label = ''; if (!empty($element['#title'])) { $label .= ''; } $output = '
'. $label; foreach (element_children($element) as $key) { $output .= drupal_render($element[$key]); } return $output; } function theme_multigroup_display_table($element) { $label = ''; if (!empty($element['#title'])) { $label .= ''; } $output = $label; foreach (element_children($element) as $key) { $output .= drupal_render($element[$key]); } return $output; }