This adds multigroup support to CCK-2.
Note: this version has bugs and requires patches, namely the patch called: cck-6.x-3.x-workaround_multigroup_bug-1.patch
diff -Nurp ../cck.orig/modules/content_multigroup/README.txt ./modules/content_multigroup/README.txt
--- ../cck.orig/modules/content_multigroup/README.txt 2010-05-05 09:16:28.000000000 -0500
+++ ./modules/content_multigroup/README.txt 2010-05-05 09:39:21.000000000 -0500
@@ -1,4 +1,100 @@
-; $Id: README.txt,v 1.1.2.4 2009/06/04 18:57:59 yched Exp $
+; $Id: README.txt,v 1.1.2.4.2.3 2009/08/10 03:53:24 markuspetrux Exp $
-Ongoing work on the multigroup module has moved to the experimental
-CCK 3.0 branch.
+CONTENTS OF THIS FILE
+=====================
+- USING MULTIGROUPS
+- FIELDS AND WIDGETS THAT WORK IN MULTIGROUPS
+- VIEWS INTEGRATION
+- TROUBLESHOOTING
+
+
+USING MULTIGROUPS
+=================
+
+The Multigroup group treats all included fields like a single field, keeping
+the related delta values of all included fields synchronized.
+
+To use a Multigroup, create a new group, make it the 'Multigroup' type, set
+the number of multiple values for all the fields in the Multigroup, and drag
+into it the fields that should be included.
+
+All fields in the Multigroup will automatically get the group setting for
+multiple values. On the node form, the group is rearranged to keep the delta
+values for each field in a single drag 'n drop group, by transposing the
+normal array(group_name => field_name => delta => value) into
+array(group_name => delta => field_name => value).
+
+During validation and submission, the field values are restored to their
+normal positions.
+
+
+FIELDS AND WIDGETS THAT WORK IN MULTIGROUPS
+===========================================
+
+All fields that allow the Content module to handle their multiple values should
+work here. Fields that handle their own multiple values will not be allowed
+into Multigroups unless they implement hook_content_multigroup_allowed_widgets()
+to add their widgets to the allowed widget list. Example:
+
+ @code
+ function MODULE_content_multigroup_allowed_widgets() {
+ return array('WIDGET_NAME_1', 'WIDGET_NAME_2', ...);
+ }
+ @endcode
+
+All fields that allow the Content module to handle their multiple values
+should work correctly when a field placed on a Multigroup is moved off, to a
+normal field group, or to the top level of the form. Fields that handle their
+own multiple values which may store different results in Multigroup and
+standard groups should implement hook_content_multigroup_no_remove_widgets()
+to add their widgets to the list of widgets that cannot be removed from
+Multigroups. Example:
+
+ @code
+ function MODULE_content_multigroup_no_remove_widgets() {
+ return array('WIDGET_NAME_1', 'WIDGET_NAME_2', ...);
+ }
+ @endcode
+
+The Content Taxonomy module [1] is an example where it implements the previous
+hooks for a few widgets.
+
+[1] http://drupal.org/project/content_taxonomy
+
+If a simple array of widgets is not sufficient to test whether this action
+will work, modules can implement hook_content_multigroup_allowed_in()
+and hook_content_multigroup_allowed_out() to intervene. Both hooks should
+return an array as in the following example:
+
+ @code
+ function MODULE_content_multigroup_allowed_in() {
+ return array(
+ 'allowed' => FALSE,
+ 'message' => t('This change is not allowed. Reason here...'),
+ );
+ }
+ @endcode
+
+Custom code and modules that add fields to groups outside of the UI should
+use content_multigroup_allowed_in() and content_multigroup_allowed_out() to
+test whether fields are allowed in or out of a Multigroup. These functions
+can be located in content_multigroup.admin.inc.
+
+
+VIEWS INTEGRATION
+=================
+
+For each multigroup, there is a new filter under "Content multigroup" category
+in Views that provides a method to synchronize fields by delta.
+
+
+TROUBLESHOOTING
+===============
+
+The most likely cause of problems with field modules not working in multigroup
+is if they wipe out #element_validate with their own validation functions, or
+they hard-code assumptions into submit or validation processes that the form
+is structured in the usual field => delta => value order instead of allowing
+for the possibility of a different structure. See Nodereference for an example
+of a field that handles validation without making assumptions about the form
+structure.
diff -Nurp ../cck.orig/modules/content_multigroup/content_multigroup.admin.inc ./modules/content_multigroup/content_multigroup.admin.inc
--- ../cck.orig/modules/content_multigroup/content_multigroup.admin.inc 1969-12-31 18:00:00.000000000 -0600
+++ ./modules/content_multigroup/content_multigroup.admin.inc 2010-05-05 09:39:21.000000000 -0500
@@ -0,0 +1,507 @@
+ t('N/A'),
+ 1 => t('Unlimited'),
+ 0 => 1) + drupal_map_assoc(range(2, 10));
+}
+
+/**
+ * Validation for creating/moving fields and groups on the
+ * Manage Fields screen.
+ */
+function content_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($group['group_type']);
+ $validation = fieldgroup_validate_name($group, $form['#type_name']);
+
+ // 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'];
+ }
+ }
+ }
+
+ $rebuild = FALSE;
+ 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 = content_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);
+ $group_multiple = $groups[$new_group]['settings']['multigroup']['multiple'];
+ $multiple_values = content_multigroup_multiple_values();
+ $field = $content_type['fields'][$field_name];
+ $field['multiple'] = $group_multiple;
+ $field = content_field_instance_collapse($field);
+ content_field_instance_update($field, FALSE);
+ $rebuild = TRUE;
+ 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_values[$group_multiple], '%group' => $groups[$new_group]['label'])));
+ }
+ }
+ elseif (!empty($old_group) && isset($groups[$old_group]) && $groups[$old_group]['group_type'] == 'multigroup') {
+ $allowed_out = content_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']);
+ }
+ }
+ }
+
+ // Clear caches and rebuild menu only if any field has been updated.
+ if ($rebuild) {
+ content_clear_type_cache(TRUE);
+ menu_rebuild();
+ }
+}
+
+/**
+ * Helper function for deciding if a field is
+ * allowed into a Multigroup.
+ */
+function content_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_multiple = $group['settings']['multigroup']['multiple'];
+ $multiple_values = content_multigroup_multiple_values();
+ if ($group_multiple != 1 && $max_existing > $group_multiple) {
+ 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' => $multiple_values[$group_multiple]))
+ );
+ }
+
+ // 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',
+ 'nodereference_buttons',
+ 'nodereference_select',
+ 'userreference_buttons',
+ 'userreference_select',
+ );
+ $allowed_widgets = array_merge($allowed_widgets, module_invoke_all('content_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('content_multigroup_allowed_in') as $module) {
+ $function = $module .'_content_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 content_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',
+ 'nodereference_buttons',
+ 'nodereference_select',
+ 'userreference_buttons',
+ 'userreference_select',
+ );
+ $no_remove_widgets = array_merge($no_remove_widgets, module_invoke_all('content_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('content_multigroup_allowed_out') as $module) {
+ $function = $module .'_content_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);
+}
+
+/**
+ * Alter the basic field settings form.
+ *
+ * It should not be possible to choose a widget type that is not compatible
+ * with multigroups.
+ */
+function content_multigroup_field_basic_form(&$form, &$form_state) {
+ $field_name = $form['basic']['field_name']['#value'];
+ $type_name = $form['type_name']['#value'];
+
+ // Ignore this field if it is not part of a field group.
+ if (!($group_name = fieldgroup_get_group($type_name, $field_name))) {
+ return;
+ }
+
+ // Retrieve information about the group the field is in.
+ $groups = fieldgroup_groups($type_name);
+ $group = $groups[$group_name];
+
+ // Ignore this field if it is not part of a multigroup.
+ if ($group['group_type'] != 'multigroup') {
+ return;
+ }
+
+ // Retrieve information about the field itself.
+ $field = content_fields($field_name, $type_name);
+
+ // Check if the widget can be moved out of the multigroup.
+ $allowed_out = content_multigroup_allowed_out($field, $group);
+ if (!$allowed_out['allowed']) {
+ $form['basic']['widget_type']['#disabled'] = TRUE;
+ $form['basic']['widget_type']['#suffix'] = '
'. t('The widget type cannot be changed because the field %field already has data created and this widget stores data differently in a Standard group than in a Multigroup. Allowing this change could result in the loss of data.', array('%field' => $field['widget']['label'])) .'
';
+ return;
+ }
+
+ // Remove from the list of available widgets those that are not
+ // compatible with multigroups.
+ $widget_types = _content_widget_types();
+ foreach (array_keys($form['basic']['widget_type']['#options']) as $widget_type) {
+ if ($field['widget']['type'] != $widget_type) {
+ $field_copy = $field;
+ $field_copy['widget']['type'] = $widget_type;
+ $field_copy['widget']['module'] = $widget_types[$widget_type]['module'];
+ $allowed_in = content_multigroup_allowed_in($field_copy, $group);
+ if (!$allowed_in['allowed']) {
+ unset($form['basic']['widget_type']['#options'][$widget_type]);
+ }
+ }
+ }
+}
+
+/**
+ * Alter the "Display fields" form.
+ *
+ * Add an additional selector for setting multigroup field display format.
+ */
+function content_multigroup_display_overview_form(&$form, &$form_state) {
+
+ $type_name = $form['#type_name'];
+ $contexts_selector = $form['#contexts'];
+
+ // Gather type information.
+ $content_type = content_types($type_name);
+
+ // The content module stops building the form if the type has no fields.
+ if (empty($content_type['fields'])) {
+ return;
+ }
+
+ $contexts = content_build_modes($contexts_selector);
+
+ // Multigroups, extra values.
+ $label_options = array(
+ 'above' => t('Above'),
+ 'hidden' => t(''),
+ );
+ $options = array(
+ 'simple' => t('Simple'),
+ 'fieldset' => t('Fieldset'),
+ 'fieldset_collapsible' => t('Fieldset - collapsible'),
+ 'fieldset_collapsed' => t('Fieldset - collapsed'),
+ 'hr' => t('Horizontal line'),
+ 'table-single' => t('Table - Single column'),
+ 'table-multiple' => t('Table - Multiple columns'),
+ );
+ foreach (fieldgroup_groups($type_name) as $group_name => $group) {
+ if ($group['group_type'] != 'multigroup') {
+ continue;
+ }
+ $subgroup_settings = isset($group['settings']['multigroup']['subgroup']) ? $group['settings']['multigroup']['subgroup'] : array();
+
+ $subgroup_name = $group_name .'_subgroup';
+ $form['#fields'] = array_merge(array($subgroup_name), $form['#fields']);
+ $form[$subgroup_name] = array(
+ 'human_name' => array('#value' => t('[Subgroup format]')),
+ 'weight' => array('#type' => 'value', '#value' => -20),
+ 'parent' => array('#type' => 'value', '#value' => $group_name),
+ 'subgroup' => array('#type' => 'value', '#value' => 1),
+ );
+ if ($contexts_selector == 'basic') {
+ $form[$subgroup_name]['label'] = array(
+ '#type' => 'select',
+ '#options' => $label_options,
+ '#default_value' => isset($subgroup_settings['label']) ? $subgroup_settings['label'] : 'above',
+ );
+ }
+ foreach ($contexts as $key => $title) {
+ $form[$subgroup_name][$key]['format'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#default_value' => isset($subgroup_settings[$key]['format']) ? $subgroup_settings[$key]['format'] : 'fieldset',
+ );
+ $form[$subgroup_name][$key]['exclude'] = array('#type' => 'value', '#value' => 0);
+ }
+ }
+
+ $form['#submit'] = array_merge(array('content_multigroup_display_overview_form_submit'), $form['#submit']);
+}
+
+/**
+ * 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 content_multigroup_display_overview_form_submit($form, &$form_state) {
+ $groups = fieldgroup_groups($form['#type_name']);
+ $reset_cache = FALSE;
+
+ // Find any subgroups we inserted into the display fields form,
+ // save our settings, and remove them from $form_state.
+ foreach ($form_state['values'] as $key => $values) {
+ if (in_array($key, $form['#fields']) && !empty($values['parent']) && !empty($values['subgroup'])) {
+ $group_name = $values['parent'];
+ $group = $groups[$group_name];
+ unset($values['subgroup'], $values['parent']);
+
+ // We have some numeric keys here, so we can't use array_merge.
+ foreach ($values as $k => $v) {
+ $group['settings']['multigroup']['subgroup'][$k] = $v;
+ }
+
+ // Update the group information in the database. Note that
+ // 'fieldgroup_data' in cache tables are also cleared here,
+ // but we need to reset static caches of fieldgroup_groups().
+ fieldgroup_save_group($form['#type_name'], $group);
+ $reset_cache = TRUE;
+
+ // Remove the subgroup from $form_state.
+ unset($form_state['values'][$key]);
+ }
+ }
+ if ($reset_cache) {
+ fieldgroup_groups('', FALSE, TRUE);
+ }
+}
+
+/**
+ * Alter the Fieldgroup edit form to add Multigroup settings.
+ */
+function content_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($type_name);
+ $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('Multigroup settings'),
+ '#collapsed' => FALSE,
+ '#collapsible' => TRUE,
+ );
+
+ if (isset($group['settings']['multigroup']['subgroup'])) {
+ // Preserve subgroup display settings.
+ $form['settings']['multigroup']['subgroup'] = array(
+ '#type' => 'value',
+ '#value' => $group['settings']['multigroup']['subgroup'],
+ );
+ }
+
+ $form['settings']['multigroup']['multiple-columns'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Multiple columns'),
+ '#default_value' => isset($group['settings']['multigroup']['multiple-columns']) ? $group['settings']['multigroup']['multiple-columns'] : 0,
+ '#description' => t('Enable this option to render each field on a separate column on the node edit form.'),
+ );
+
+ $form['settings']['multigroup']['required'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Required'),
+ '#default_value' => isset($group['settings']['multigroup']['required']) ? $group['settings']['multigroup']['required'] : 1,
+ '#description' => t('Enable this option to require a minimum of one collection of fields in this Multigroup.'),
+ );
+
+ $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 items as many times as they like.") .' ';
+ $description .= t('All fields in this group will automatically be set to allow this number of values.');
+
+ $group_multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1;
+ $form['settings']['multigroup']['multiple'] = array(
+ '#type' => 'select',
+ '#title' => t('Number of repeats'),
+ '#options' => content_multigroup_multiple_values(),
+ '#default_value' => $group_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 ($group_multiple < 2) {
+ $group_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'][] = 'content_multigroup_group_edit_form_validate';
+ $form['#submit'][] = 'content_multigroup_group_edit_form_submit';
+}
+
+/**
+ * Validate the Fieldgroup edit form.
+ */
+function content_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 content_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']];
+ $group_fields = array_intersect_key($content_type['fields'], $group['fields']);
+ if (!empty($group_fields)) {
+ foreach ($group_fields as $field_name => $field) {
+ $field['multiple'] = $form_values['settings']['multigroup']['multiple'];
+ $field = content_field_instance_collapse($field);
+ content_field_instance_update($field, FALSE);
+ }
+ content_clear_type_cache(TRUE);
+ menu_rebuild();
+ }
+}
diff -Nurp ../cck.orig/modules/content_multigroup/content_multigroup.css ./modules/content_multigroup/content_multigroup.css
--- ../cck.orig/modules/content_multigroup/content_multigroup.css 1969-12-31 18:00:00.000000000 -0600
+++ ./modules/content_multigroup/content_multigroup.css 2010-05-05 09:39:21.000000000 -0500
@@ -0,0 +1,42 @@
+/* $Id: content_multigroup.css,v 1.1.4.2 2009/06/07 00:06:21 markuspetrux Exp $ */
+
+label.content-multigroup {
+ font-weight: bold;
+}
+
+/* Not styled by default, but available to style. */
+hr.content-multigroup {
+}
+
+/* Inline field labels visible within the context of multigroups. */
+.content-multigroup-wrapper .field .field-label-inline {
+ visibility: visible;
+}
+
+/**
+ * Hide field labels and description on the node edit form when the multiple
+ * columns option is enabled.
+ */
+.content-multigroup-edit-table-multiple-columns label,
+.content-multigroup-edit-table-multiple-columns .description {
+ display: none;
+}
+
+/* Hide field labels when using 'table-multiple' display mode. */
+.content-multigroup-display-table-multiple-columns .field .field-label,
+.content-multigroup-display-table-multiple-columns .field .field-label-inline,
+.content-multigroup-display-table-multiple-columns .field .field-label-inline-first {
+ display: none;
+}
+
+/* Display table with a row for each subgroup and all fields in a single column. */
+.content-multigroup-display-table-single-column .content-multigroup-wrapper {
+ clear: both;
+}
+.content-multigroup-display-table-single-column .content-multigroup-wrapper label.content-multigroup {
+ display: block;
+}
+.content-multigroup-display-table-single-column .content-multigroup-wrapper .field {
+ float: left;
+ margin-right: 1em;
+}
diff -Nurp ../cck.orig/modules/content_multigroup/content_multigroup.info ./modules/content_multigroup/content_multigroup.info
--- ../cck.orig/modules/content_multigroup/content_multigroup.info 1969-12-31 18:00:00.000000000 -0600
+++ ./modules/content_multigroup/content_multigroup.info 2010-05-05 09:39:21.000000000 -0500
@@ -0,0 +1,13 @@
+; $Id: content_multigroup.info,v 1.1.2.2 2009/06/07 01:16:40 markuspetrux Exp $
+name = Content Multigroup
+description = Combine multiple CCK fields into repeating field collections that work in unison.
+dependencies[] = content
+dependencies[] = fieldgroup
+package = CCK
+core = 6.x
+; Information added by drupal.org packaging script on 2010-01-26
+version = "6.x-3.x-dev"
+core = "6.x"
+project = "cck"
+datestamp = "1264464167"
+
diff -Nurp ../cck.orig/modules/content_multigroup/content_multigroup.install ./modules/content_multigroup/content_multigroup.install
--- ../cck.orig/modules/content_multigroup/content_multigroup.install 1969-12-31 18:00:00.000000000 -0600
+++ ./modules/content_multigroup/content_multigroup.install 2010-05-05 09:39:21.000000000 -0500
@@ -0,0 +1,47 @@
+ 2,
+ 'path' => drupal_get_path('module', 'content_multigroup') . '/views',
+ );
+}
+
+/**
+ * Implementation of hook_ctools_plugin_directory().
+ */
+function content_multigroup_ctools_plugin_directory($module, $plugin) {
+ if ($module == 'ctools' && $plugin == 'content_types') {
+ return 'panels/' . $plugin;
+ }
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function content_multigroup_menu() {
+ $items = array();
+ // Callback for AHAH add more buttons.
+ $items['content_multigroup/js_add_more'] = array(
+ 'page callback' => 'content_multigroup_add_more_js',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'content_multigroup.node_form.inc',
+ );
+ return $items;
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function content_multigroup_theme() {
+ return array(
+ 'content_multigroup_node_form' => array(
+ 'arguments' => array('element' => NULL),
+ 'file' => 'content_multigroup.node_form.inc',
+ ),
+ 'content_multigroup_node_label' => array(
+ 'arguments' => array('text' => NULL),
+ 'file' => 'content_multigroup.node_form.inc',
+ ),
+ 'content_multigroup_display_simple' => array(
+ 'arguments' => array('element' => NULL),
+ 'file' => 'content_multigroup.node_view.inc',
+ ),
+ 'content_multigroup_display_fieldset' => array(
+ 'arguments' => array('element' => NULL),
+ 'file' => 'content_multigroup.node_view.inc',
+ ),
+ 'content_multigroup_display_hr' => array(
+ 'arguments' => array('element' => NULL),
+ 'file' => 'content_multigroup.node_view.inc',
+ ),
+ 'content_multigroup_display_table_single' => array(
+ 'arguments' => array('element' => NULL),
+ 'file' => 'content_multigroup.node_view.inc',
+ ),
+ 'content_multigroup_display_table_multiple' => array(
+ 'arguments' => array('element' => NULL),
+ 'file' => 'content_multigroup.node_view.inc',
+ ),
+ );
+}
+
+/**
+ * Implementation of hook_elements().
+ */
+function content_multigroup_elements() {
+ return array(
+ 'content_multigroup_display_fieldset' => array('#value' => NULL),
+ );
+}
+
+/**
+ * Implementation of hook_fieldgroup_types().
+ */
+function content_multigroup_fieldgroup_types() {
+ return array('multigroup' => t('Multigroup'));
+}
+
+/**
+ * Implementation of hook_fieldgroup_default_settings().
+ */
+function content_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['display'][$key]['format'] = 'fieldset';
+ }
+ return $settings;
+ }
+}
+
+/**
+ * Implementation of hook_form_alter().
+ */
+function content_multigroup_form_alter(&$form, $form_state, $form_id) {
+ if ($form_id == 'content_field_edit_form' && isset($form['widget'])) {
+ // If this is a field edit form and the field is in a Multigroup,
+ // override the multiple value settings.
+ $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_edit_form' && isset($form_state['change_basic'])) {
+ // This is the basic field settings form. It should not be possible to
+ // choose a widget type that is not compatible with multigroups.
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.admin');
+ content_multigroup_field_basic_form($form, $form_state);
+ }
+ elseif ($form_id == 'content_field_overview_form') {
+ // Validation for creating/moving fields and groups on the
+ // Manage Fields screen.
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.admin');
+ $form['#validate'][] = 'content_multigroup_field_overview_form_validate';
+ }
+ elseif ($form_id == 'content_display_overview_form' && !empty($form['#groups'])) {
+ // Add an additional selector for setting multigroup field display
+ // format to the Display Fields screen.
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.admin');
+ content_multigroup_display_overview_form($form, $form_state);
+ }
+ elseif ($form_id == 'fieldgroup_group_edit_form') {
+ // Alter the Fieldgroup edit form to add Multigroup settings.
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.admin');
+ content_multigroup_group_edit_form($form, $form_state);
+ }
+}
+
+/**
+ * After build callback for multigroups in node form.
+ *
+ * This proxy function is necessary to prevent from breaking AHAH handlers.
+ */
+function content_multigroup_node_form_after_build($form, &$form_state) {
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.node_form');
+ return _content_multigroup_node_form_after_build($form, $form_state);
+}
+
+/**
+ * Implementation of hook_fieldgroup_form().
+ */
+function content_multigroup_fieldgroup_form(&$form, &$form_state, $form_id, $group) {
+ $group_name = $group['group_name'];
+ if ($group['group_type'] == 'multigroup' && !empty($form[$group_name])) {
+ if (!isset($form[$group_name]['#access']) || $form[$group_name]['#access']) {
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.node_form');
+ _content_multigroup_fieldgroup_form($form, $form_state, $form_id, $group);
+ }
+ }
+}
+
+/**
+ * Implementation of hook_fieldgroup_view().
+ */
+function content_multigroup_fieldgroup_view(&$node, &$element, $group, $context) {
+ if ($group['group_type'] == 'multigroup') {
+ module_load_include('inc', 'content_multigroup', 'content_multigroup.node_view');
+ _content_multigroup_fieldgroup_view($node, $element, $group, $context);
+ }
+}
diff -Nurp ../cck.orig/modules/content_multigroup/content_multigroup.node_form.inc ./modules/content_multigroup/content_multigroup.node_form.inc
--- ../cck.orig/modules/content_multigroup/content_multigroup.node_form.inc 1969-12-31 18:00:00.000000000 -0600
+++ ./modules/content_multigroup/content_multigroup.node_form.inc 2010-05-05 09:39:21.000000000 -0500
@@ -0,0 +1,936 @@
+ $field) {
+ if (isset($group['fields'][$field_name]) && isset($form[$group_name][$field_name])) {
+ if (!isset($form[$group_name][$field_name]['#access']) || $form[$group_name][$field_name]['#access']) {
+ $group_fields[$field_name] = $field;
+ }
+ }
+ }
+
+ // Quit if there are no field in the form for this group.
+ if (empty($group_fields)) {
+ return;
+ }
+
+ switch ($group_multiple) {
+ case 0:
+ $group_deltas = array(0);
+ $max_delta = 0;
+ break;
+
+ case 1:
+ // Compute unique deltas from all deltas used by fields in this multigroup.
+ $group_deltas = array();
+ $max_delta = -1;
+ foreach (array_keys($group_fields) as $field_name) {
+ if (!empty($node->$field_name) && is_array($node->$field_name)) {
+ foreach (array_keys($node->$field_name) as $delta) {
+ $group_deltas[$delta] = $delta;
+ }
+ sort($group_deltas);
+ $max_delta = max($max_delta, max($group_deltas));
+ }
+ }
+ $current_item_count = isset($form_state['item_count'][$group_name]) ? $form_state['item_count'][$group_name] : max(1, count($group_deltas));
+ while (count($group_deltas) < $current_item_count) {
+ $max_delta++;
+ $group_deltas[] = $max_delta;
+ }
+ break;
+
+ default:
+ $max_delta = $group_multiple - 1;
+ $group_deltas = range(0, $max_delta);
+ break;
+ }
+
+ $form[$group_name]['#theme'] = 'content_multigroup_node_form';
+ $form[$group_name]['#item_count'] = count($group_deltas);
+ $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]['#group_fields'] = $group_fields;
+ $form[$group_name]['#tree'] = TRUE;
+ if (!isset($form['#multigroups'])) {
+ $form['#multigroups'] = array();
+ }
+ $form['#multigroups'][$group_name] = $group_fields;
+
+ // Add a visual indication to the fieldgroup title if the multigroup is required.
+ if (!empty($group['settings']['multigroup']['required'])) {
+ $form[$group_name]['#title'] .= ' *';
+ }
+
+ // Attach our own after build handler to the form, used to fix posting data
+ // and the form structure, moving fields back to their original positions.
+ // That is, move them from group->delta->field back to field->delta.
+ if (!isset($form['#after_build'])) {
+ $form['#after_build'] = array();
+ }
+ if (!in_array('content_multigroup_node_form_after_build', $form['#after_build'])) {
+ array_unshift($form['#after_build'], 'content_multigroup_node_form_after_build');
+ }
+
+ // Attach our own validation handler to the form, used to check for empty fields.
+ if (!isset($form['#validate'])) {
+ $form['#validate'] = array();
+ }
+ if (!in_array('content_multigroup_node_form_validate', $form['#validate'])) {
+ array_unshift($form['#validate'], 'content_multigroup_node_form_validate');
+ }
+
+ // Attach our own pre_render handler to the form, used to fix the required
+ // attribute of all fields in multigroups.
+ if (!isset($form['#pre_render'])) {
+ $form['#pre_render'] = array();
+ }
+ if (!in_array('content_multigroup_node_form_pre_render', $form['#pre_render'])) {
+ array_unshift($form['#pre_render'], 'content_multigroup_node_form_pre_render');
+ }
+
+ foreach ($group_deltas as $delta) {
+ content_multigroup_group_form($form, $form_state, $group, $delta);
+ }
+
+ // Unset the original group field values now that we've moved them.
+ foreach (array_keys($group_fields) as $field_name) {
+ unset($form[$group_name][$field_name]);
+ }
+
+ if (($add_more = content_multigroup_add_more($form, $form_state, $group)) !== FALSE) {
+ $form[$group_name] += $add_more;
+ }
+}
+
+/**
+ * Create a new delta value for the group.
+ *
+ * Called in form_alter and by AHAH add more.
+ */
+function content_multigroup_group_form(&$form, &$form_state, $group, $delta) {
+ $group_name = $group['group_name'];
+ if ($group['group_type'] != 'multigroup' || empty($form[$group_name]) || empty($form['#multigroups']) || empty($form['#multigroups'][$group_name])) {
+ return;
+ }
+ module_load_include('inc', 'content', 'includes/content.node_form');
+
+ $node = $form['#node'];
+ $group_fields = $form['#multigroups'][$group_name];
+ $group_multiple = $group['settings']['multigroup']['multiple'];
+
+ foreach ($group_fields as $field_name => $field) {
+ if (empty($form[$group_name][$delta])) {
+ $form[$group_name] += array($delta => array($field_name => array()));
+ }
+ else {
+ $form[$group_name][$delta][$field_name] = array();
+ }
+
+ $item_count = (isset($form_state['item_count'][$group_name]) ? $form_state['item_count'][$group_name] : $form[$group_name]['#item_count']);
+ $form[$group_name][$delta]['_weight'] = array(
+ '#type' => 'weight',
+ '#delta' => $item_count, // this 'delta' is the 'weight' element's property
+ '#default_value' => $delta,
+ '#weight' => 100,
+ );
+
+ // Add a checkbox to allow users remove a single delta subgroup.
+ // See content_set_empty() and theme_content_multigroup_node_form().
+ if ($group_multiple == 1) {
+ $form[$group_name][$delta]['_remove'] = array(
+ '#type' => 'checkbox',
+ '#attributes' => array('class' => 'content-multiple-remove-checkbox'),
+ '#default_value' => isset($form_state['multigroup_removed'][$group_name][$delta]) ? $form_state['multigroup_removed'][$group_name][$delta] : 0,
+ );
+ }
+
+ // Make each field into a pseudo single value field
+ // with the right delta value.
+ $field['multiple'] = 0;
+
+ $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) && isset($node->{$field_name}[$delta])) {
+ $node_copy->$field_name = array($delta => $node->{$field_name}[$delta]);
+ }
+ else {
+ $value = NULL;
+ // Try to obtain default values only if the node is being created.
+ if (!isset($node->nid) && content_callback('widget', 'default value', $field) != CONTENT_CALLBACK_NONE) {
+ // If a module wants to insert custom default values here,
+ // it should provide a hook_default_value() function to call,
+ // otherwise the content module's content_default_value() function
+ // will be used.
+ $callback = content_callback('widget', 'default value', $field) == CONTENT_CALLBACK_CUSTOM ? $field['widget']['module'] .'_default_value' : 'content_default_value';
+ if (function_exists($callback)) {
+ $items = $callback($form, $form_state, $field, 0);
+ $value = $items[0];
+ }
+ }
+ $node_copy->$field_name = array($delta => $value);
+ }
+ $form['#node'] = $node_copy;
+
+ // Place the new element into the $delta position in the group form.
+ if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
+ $field_form = content_field_form($form, $form_state, $field, $delta);
+ $value = array_key_exists($delta, $field_form[$field_name]) ? $delta : 0;
+ $form[$group_name][$delta][$field_name] = $field_form[$field_name][$value];
+ }
+ else {
+ // When the form is submitted, get the element data from the form values.
+ if (isset($form_state['values'][$field_name])) {
+ $form_state_copy = $form_state;
+ if (isset($form_state_copy['values'][$field_name][$delta])) {
+ $form_state_copy['values'][$field_name] = array($delta => $form_state_copy['values'][$field_name][$delta]);
+ }
+ else {
+ $form_state_copy['values'][$field_name] = array($delta => NULL);
+ }
+ $field_form = content_field_form($form, $form_state_copy, $field, $delta);
+ }
+ else {
+ $field_form = content_field_form($form, $form_state, $field, $delta);
+ }
+
+ // Multiple value fields have an additional level in the array form that
+ // needs to get fixed in $form_state['values'].
+ if (!isset($field_form[$field_name]['#element_validate'])) {
+ $field_form[$field_name]['#element_validate'] = array();
+ }
+ $field_form[$field_name]['#element_validate'][] = 'content_multigroup_fix_multivalue_fields';
+
+ $form[$group_name][$delta][$field_name] = $field_form[$field_name];
+ }
+ $form[$group_name][$delta][$field_name]['#weight'] = $field['widget']['weight'];
+ }
+
+ // Reset the form '#node' back to its original value.
+ $form['#node'] = $node;
+}
+
+/**
+ * Fix required flag during form rendering stage.
+ *
+ * Required fields should display the required star in the rendered form.
+ */
+function content_multigroup_node_form_pre_render(&$form) {
+ foreach ($form['#multigroups'] as $group_name => $group_fields) {
+ $required_fields = array();
+ foreach ($group_fields as $field_name => $field) {
+ if ($field['required']) {
+ $required_fields[] = $field_name;
+ }
+ }
+ if (!empty($required_fields)) {
+ content_multigroup_node_form_fix_required($form[$group_name], $required_fields, TRUE);
+ }
+ }
+ return $form;
+}
+
+/**
+ * Fix form and posting data when the form is submitted.
+ *
+ * FormAPI uses form_builder() during form processing to map incoming $_POST
+ * data to the proper elements in the form. It builds the '#parents' array,
+ * copies the $_POST array to the '#post' member of all form elements, and it
+ * also builds the $form_state['values'] array. Then the '#after_build' hook is
+ * invoked to allow custom processing of the form structure, and that happens
+ * just before validation and submit handlers are executed.
+ *
+ * During hook_form_alter(), the multigroup module altered the form structure
+ * moving elements from field->delta to multigroup->delta->field position,
+ * which is what has been processed by FormAPI to build the form structures,
+ * but field validation (and submit) handlers expect their data to be located
+ * in their original positions.
+ *
+ * We now need to move the fields back to their original positions in the form,
+ * and we need to do so without altering the form rendering process, which is
+ * now reflecting the structure the multigroup is interested in. We just need
+ * to fix the parts of the form that affect validation and submit processing.
+ */
+function _content_multigroup_node_form_after_build($form, &$form_state) {
+ // Disable required flag during FormAPI validation, except when building the
+ // form for an 'Add more values' request.
+ $required = !empty($form_state['multigroup_add_more']);
+ foreach ($form['#multigroups'] as $group_name => $group_fields) {
+ $required_fields = array();
+ foreach ($group_fields as $field_name => $field) {
+ if ($field['required']) {
+ $required_fields[] = $field_name;
+ }
+ }
+ if (!empty($required_fields)) {
+ content_multigroup_node_form_fix_required($form[$group_name], $required_fields, $required);
+ }
+ }
+
+ if ($form_state['submitted']) {
+ // Fix value positions in $form_state for the fields in multigroups.
+ foreach (array_keys($form['#multigroups']) as $group_name) {
+ content_multigroup_node_form_transpose_elements($form, $form_state, $form['#node']->type, $group_name);
+ }
+
+ // Fix form element parents for all fields in multigroups.
+ content_multigroup_node_form_fix_parents($form, $form['#multigroups']);
+
+ // Update posting data to reflect delta changes in the form structure.
+ if (!empty($_POST)) {
+ content_multigroup_node_form_fix_post($form);
+ }
+ }
+
+ return $form;
+}
+
+/**
+ * Fix required flag for required fields.
+ *
+ * We need to let the user enter an empty set of fields for a delta subgroup,
+ * even if it contains required fields, which is equivalent to say a subgroup
+ * should be ignored, not to be stored into the database.
+ * So, we need to check for required fields, but only for non-empty subgroups.
+ *
+ * When the form is processed for rendering, the required flag is enabled for
+ * all required fields, so the user can see what's required and what's not.
+ *
+ * When the form is processed for validation, the required flag is disabled,
+ * so that FormAPI does not report errors for empty fields.
+ *
+ * @see content_multigroup_node_form_validate().
+ */
+function content_multigroup_node_form_fix_required(&$elements, $required_fields, $required) {
+ foreach (element_children($elements) as $key) {
+ if (isset($elements[$key]) && $elements[$key]) {
+
+ if (count($elements[$key]['#array_parents']) >= 3 && in_array($elements[$key]['#array_parents'][2], $required_fields) && isset($elements[$key]['#required'])) {
+ $elements[$key]['#required'] = $required;
+ }
+
+ // Recurse through all children elements.
+ content_multigroup_node_form_fix_required($elements[$key], $required_fields, $required);
+ }
+ }
+}
+
+/**
+ * Node form validation handler.
+ *
+ * Perform validation for empty fields ignoring subgroups flagged for removal.
+ * Note that FormAPI validation for required fields is disabled because we need
+ * to accept empty fields that are flagged for removal.
+ */
+function content_multigroup_node_form_validate($form, &$form_state) {
+ $type_name = $form['#node']->type;
+ $groups = fieldgroup_groups($type_name);
+
+ foreach ($form['#multigroups'] as $group_name => $group_fields) {
+ $group = $groups[$group_name];
+ $group_required = isset($group['settings']['multigroup']['required']) ? $group['settings']['multigroup']['required'] : 1;
+
+ $non_empty_subgroups = $non_removed_subgroups = $required_field_errors = array();
+ foreach ($group_fields as $field_name => $field) {
+ // Tell the content module that it is not needed to enforce requirement
+ // of fields in this multigroup because we are doing it here.
+ // See content_multiple_value_nodeapi_validate().
+ $form_state['values']['_content_ignore_required_fields'][$field_name] = TRUE;
+
+ foreach ($form_state['values'][$field_name] as $delta => $item) {
+ // Ignore subgroups flagged for removal.
+ if ($form_state['multigroup_removed'][$group_name][$delta]) {
+ continue;
+ }
+ // Keep track of non-removed subgroups.
+ $non_removed_subgroups[$delta] = TRUE;
+
+ $is_empty_function = $field['module'] .'_content_is_empty';
+ if ($is_empty_function($form_state['values'][$field_name][$delta], $field)) {
+ // Ignore fields that are not required.
+ if (!$field['required']) {
+ continue;
+ }
+
+ // Build an error message for this field in this subgroup, but do
+ // not flag it, yet.
+ if (!empty($item['_error_element'])) {
+ // Here we don't know the number of elements and subelements a
+ // widget could have added to the form, so we need to extract
+ // components from the top, where we have group/delta/field, and
+ // then push back field/delta on top of the list.
+ $error_element = explode('][', $item['_error_element']);
+ array_shift($error_element);
+ array_shift($error_element);
+ array_shift($error_element);
+ array_unshift($error_element, $field_name, $delta);
+ $error_element = implode('][', $error_element);
+ }
+ else {
+ $error_element = '';
+ }
+ $required_field_errors[$delta][$field_name] = array(
+ 'element' => $error_element,
+ 'message' => t('!name field is required in group @group.', array(
+ '!name' => $form[$group_name][$delta][$field_name]['#title'],
+ '@group' => t($group['label']),
+ )),
+ );
+ }
+ else {
+ $non_empty_subgroups[$delta] = TRUE;
+ }
+ }
+ }
+
+ // Required multigroups require at least one non-empty subgroup of fields.
+ if ($group_required && empty($non_empty_subgroups)) {
+ form_set_error('', t('Group @name requires one collection of fields minimum.', array('@name' => t($group['label']))));
+ continue;
+ }
+
+ // Do not enforce field requirements if there is only one non-removed
+ // subgroup of fields, and that subgroup is empty.
+ if (count($non_removed_subgroups) == 1) {
+ $delta = key($non_removed_subgroups);
+ if (isset($required_field_errors[$delta]) && !isset($non_empty_subgroups[$delta])) {
+ unset($required_field_errors[$delta]);
+ }
+ }
+
+ // Ok, now we can flag errors for all required fields that have not been
+ // filled in when they should.
+ foreach ($required_field_errors as $delta => $error_list) {
+ foreach ($error_list as $field_name => $error_info) {
+ form_set_error($error_info['element'], $error_info['message']);
+ }
+ }
+ }
+}
+
+/**
+ * Transpose element positions in $form_state for the fields in a multigroup.
+ */
+function content_multigroup_node_form_transpose_elements(&$form, &$form_state, $type_name, $group_name) {
+ $groups = fieldgroup_groups($type_name);
+ $group = $groups[$group_name];
+ $group_fields = $form['#multigroups'][$group_name];
+
+ // Save the remove state of multigroup items in the $form_state array.
+ if (!isset($form_state['multigroup_removed'])) {
+ $form_state['multigroup_removed'] = array();
+ }
+ if (!isset($form_state['multigroup_removed'][$group_name])) {
+ $form_state['multigroup_removed'][$group_name] = array();
+ }
+
+ // Move group data from group->delta->field to field->delta.
+ $group_data = array();
+ foreach ($form_state['values'][$group_name] as $delta => $items) {
+ // Skip 'add more' button.
+ if (!is_array($items) || !isset($items['_weight'])) {
+ continue;
+ }
+ foreach ($group_fields as $field_name => $field) {
+ if (!isset($group_data[$field_name])) {
+ $group_data[$field_name] = array();
+ }
+ // Get field weight and remove state from the group and keep track of the
+ // current delta for each field item.
+ $item_defaults = array(
+ '_weight' => $items['_weight'],
+ '_remove' => $items['_remove'],
+ '_old_delta' => $delta,
+ );
+ $group_data[$field_name][$delta] = (is_array($items[$field_name]) ? array_merge($items[$field_name], $item_defaults) : $item_defaults);
+ // Store the remove state and the element weight in the form element as
+ // well, so we can restore them later.
+ // See content_multigroup_fix_multivalue_fields().
+ // See content_multigroup_fix_element_values().
+ $form[$group_name][$delta][$field_name]['#_weight'] = $items['_weight'];
+ $form[$group_name][$delta][$field_name]['#removed'] = $items['_remove'];
+
+ // Insert an element valitation callback of our own at the end of the
+ // list to ensure the drag'n'drop weight of the element is not lost by
+ // a form_set_value() operation made by the validation callback of the
+ // widget element.
+ if (!isset($form[$group_name][$delta][$field_name]['#element_validate'])) {
+ $form[$group_name][$delta][$field_name]['#element_validate'] = array();
+ }
+ $form[$group_name][$delta][$field_name]['#element_validate'][] = 'content_multigroup_fix_element_values';
+ }
+ $form_state['multigroup_removed'][$group_name][$delta] = $items['_remove'];
+ }
+
+ $form_group_sorted = FALSE;
+ foreach ($group_data as $field_name => $items) {
+
+ // Sort field items according to drag-n-drop reordering. Deltas are also
+ // rebuilt to start counting from 0 to n. Note that since all fields in the
+ // group share the same weight, their deltas remain in sync.
+ usort($items, '_content_sort_items_helper');
+
+ // Now we need to apply the same ordering to the form elements. Also,
+ // note that deltas have changed during the sort operation, so we need
+ // to reflect this delta conversion in the form.
+ if (!$form_group_sorted) {
+ $form_group_items = array();
+ $form_deltas = array();
+ foreach ($items as $new_delta => $item) {
+ $form_deltas[$item['_old_delta']] = $new_delta;
+ $form_group_items[$new_delta] = $form[$group_name][$item['_old_delta']];
+ unset($form[$group_name][$item['_old_delta']]);
+ }
+ foreach ($form_group_items as $new_delta => $form_group_item) {
+ $form[$group_name][$new_delta] = $form_group_item;
+ }
+ content_multigroup_node_form_fix_deltas($form[$group_name], $form_deltas);
+ $form_group_sorted = TRUE;
+ }
+
+ // Get rid of the old delta value.
+ foreach (array_keys($items) as $delta) {
+ unset($items[$delta]['_old_delta']);
+ }
+
+ // Fix field and delta positions in the $_POST array.
+ if (!empty($_POST)) {
+ $_POST[$field_name] = array();
+ foreach ($items as $new_delta => $item) {
+ $_POST[$field_name][$new_delta] = $item;
+ }
+ if (isset($_POST[$group_name])) {
+ unset($_POST[$group_name]);
+ }
+ }
+
+ // Move field items back to their original positions.
+ $form_state['values'][$field_name] = $items;
+ }
+
+ // Finally, get rid of the group data in form values.
+ unset($form_state['values'][$group_name]);
+}
+
+/**
+ * Fix deltas for all affected form elements.
+ */
+function content_multigroup_node_form_fix_deltas(&$elements, $form_deltas) {
+ foreach (element_children($elements) as $key) {
+ if (isset($elements[$key]) && $elements[$key]) {
+
+ // Fix the second item, the delta value, of the element's '#parents' array.
+ $elements[$key]['#parents'][1] = $form_deltas[$elements[$key]['#parents'][1]];
+
+ // If present, fix delta value in '#delta' attribute of the element.
+ if (isset($elements[$key]['#delta']) && isset($form_deltas[$elements[$key]['#delta']])) {
+ $elements[$key]['#delta'] = $form_deltas[$elements[$key]['#delta']];
+ }
+
+ // Recurse through all children elements.
+ content_multigroup_node_form_fix_deltas($elements[$key], $form_deltas);
+ }
+ }
+}
+
+/**
+ * Fix form element parents for all fields in multigroups.
+ *
+ * The $element['#parents'] array needs to reflect the position of the fields
+ * in the $form_state['values'] array so that form_set_value() can be safely
+ * used by field validation handlers.
+ */
+function content_multigroup_node_form_fix_parents(&$elements, $multigroups) {
+ foreach (element_children($elements) as $key) {
+ if (isset($elements[$key]) && $elements[$key]) {
+
+ // Check if the current element is child of a multigroup. The #parents
+ // array for field values has, at least, 3 parent elements, being the
+ // first one the name of a multigroup.
+ if (count($elements[$key]['#parents']) >= 3 && isset($multigroups[$elements[$key]['#parents'][0]])) {
+
+ // Extract group name, delta and field name from the #parents array.
+ array_shift($elements[$key]['#parents']);
+ $delta = array_shift($elements[$key]['#parents']);
+ $field_name = array_shift($elements[$key]['#parents']);
+
+ // Now, insert field name and delta to the #parents array.
+ array_unshift($elements[$key]['#parents'], $field_name, $delta);
+ }
+
+ // Recurse through all children elements.
+ content_multigroup_node_form_fix_parents($elements[$key], $multigroups);
+ }
+ }
+}
+
+/**
+ * Update posting data to reflect delta changes in the form structure.
+ *
+ * The $_POST array is fixed in content_multigroup_node_form_transpose_elements().
+ */
+function content_multigroup_node_form_fix_post(&$elements) {
+ foreach (element_children($elements) as $key) {
+ if (isset($elements[$key]) && $elements[$key]) {
+
+ // Update the element copy of the $_POST array.
+ $elements[$key]['#post'] = $_POST;
+
+ // Recurse through all children elements.
+ content_multigroup_node_form_fix_post($elements[$key]);
+ }
+ }
+
+ // Update the form copy of the $_POST array.
+ $elements['#post'] = $_POST;
+}
+
+/**
+ * Make sure the '_weight' and '_remove' attributes of the element exist.
+ *
+ * @see content_multigroup_node_form_transpose_elements()
+ */
+function content_multigroup_fix_element_values($element, &$form_state) {
+ $field_name = $element['#field_name'];
+ $delta = $element['#delta'];
+ if (!isset($form_state['values'][$field_name][$delta]['_weight']) || !isset($form_state['values'][$field_name][$delta]['_remove'])) {
+ $value = array('_weight' => $element['#_weight'], '_remove' => $element['#removed']);
+ if (isset($form_state['values'][$field_name][$delta]) && is_array($form_state['values'][$field_name][$delta])) {
+ $value = array_merge($form_state['values'][$field_name][$delta], $value);
+ }
+ form_set_value($element, $value, $form_state);
+ }
+}
+
+/**
+ * Fix the value for fields that deal with multiple values themselves.
+ */
+function content_multigroup_fix_multivalue_fields($element, &$form_state) {
+ $field_name = $element['#field_name'];
+ $delta = $element['#delta'];
+ if (isset($form_state['values'][$field_name][$delta][0]) && is_array($form_state['values'][$field_name][$delta][0])) {
+ $value = array_merge($form_state['values'][$field_name][$delta][0], array('_remove' => $element['#removed']));
+ }
+ else {
+ $value = array('_remove' => $element['#removed']);
+ }
+ form_set_value($element, $value, $form_state);
+}
+
+/**
+ * Add AHAH add more button, if not working with a programmed form.
+ */
+function content_multigroup_add_more(&$form, &$form_state, $group) {
+ $group_multiple = $group['settings']['multigroup']['multiple'];
+ if ($group_multiple != 1 || !empty($form['#programmed'])) {
+ return FALSE;
+ }
+
+ // 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 = array();
+ $form_element[$group_name .'_add_more'] = array(
+ '#type' => 'submit',
+ '#name' => $group_name .'_add_more',
+ '#value' => t('Add more values'),
+ '#weight' => $group_multiple + 1,
+ '#submit' => array('content_multigroup_add_more_submit'),
+ '#ahah' => array(
+ 'path' => 'content_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_multigroup_add_more_submit handler will
+ // find the relevant group information using these entries.
+ '#group_name' => $group_name,
+ '#type_name' => $group['type_name'],
+ '#item_count' => $form[$group_name]['#item_count'],
+ );
+
+ // Add wrappers for the group and 'more' button.
+ $form_element['#prefix'] = '
';
+
+ 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 content_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 (isset($form_state['clicked_button']['#item_count'])) {
+ $form_state['item_count'][$group_name] = $form_state['clicked_button']['#item_count'] + 1;
+ }
+}
+
+/**
+ * Menu callback for AHAH addition of new empty widgets.
+ *
+ * Adapted from content_add_more_js to work with groups instead of fields.
+ */
+function content_multigroup_add_more_js($type_name_url, $group_name) {
+ $content_type = content_types($type_name_url);
+ $groups = fieldgroup_groups($content_type['type']);
+ $group = $groups[$group_name];
+
+ if (($group['settings']['multigroup']['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]['_remove'] = isset($item['_remove']) ? $item['_remove'] : 0;
+ }
+ $group['multiple'] = $group['settings']['multigroup']['multiple'];
+ $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.
+ $delta = max(array_keys($_POST[$group_name])) + 1;
+ $form_state['item_count'] = array($group_name => count($_POST[$group_name]) + 1);
+ content_multigroup_group_form($form, $form_state, $group, $delta);
+
+ // Rebuild weight deltas to make sure they all are equally dimensioned.
+ foreach ($form[$group_name] as $key => $item) {
+ if (is_numeric($key) && isset($item['_weight']) && is_array($item['_weight'])) {
+ $form[$group_name][$key]['_weight']['#delta'] = $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, 'multigroup_add_more' => TRUE);
+ $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'] = '
';
+ }
+ return $output;
+}
+
+/**
+ * Theme a multigroup in 'table-single' format.
+ *
+ * Each subgroup has its own table row with a single cell for all fields.
+ * No output is generated if all fields are empty.
+ */
+function theme_content_multigroup_display_table_single($element) {
+ $headers = array();
+ $rows = array();
+ foreach (element_children($element) as $delta) {
+ $items = array();
+ foreach ($element['#fields'] as $field_name => $field) {
+ $item = drupal_render($element[$delta][$field_name]);
+ if (!empty($item)) {
+ $items[] = $item;
+ }
+ }
+ if (!empty($items)) {
+ if (!empty($element[$delta]['#title'])) {
+ array_unshift($items, '');
+ }
+ $rows[] = array('data' => array(implode("\n", $items)), 'class' => $element[$delta]['#attributes']['class']);
+ }
+ }
+ return count($rows) ? theme('table', $headers, $rows, $element['#attributes']) : '';
+}
+
+/**
+ * Theme a multigroup in 'table-multiple' format.
+ *
+ * Each subgroup has its own table row with a separate cell for each field.
+ * No output is generated if all fields are empty.
+ */
+function theme_content_multigroup_display_table_multiple($element) {
+ $headers = array();
+ foreach ($element['#fields'] as $field_name => $field) {
+ $label_display = isset($field['display_settings']['label']['format']) ? $field['display_settings']['label']['format'] : 'above';
+ $headers[] = array(
+ 'data' => ($label_display != 'hidden' ? check_plain(t($field['widget']['label'])) : ''),
+ 'class' => 'content-multigroup-cell-'. str_replace('_', '-', $field_name),
+ );
+ }
+ $rows = array();
+ foreach (element_children($element) as $delta) {
+ $cells = array();
+ $empty = TRUE;
+ foreach ($element['#fields'] as $field_name => $field) {
+ $item = drupal_render($element[$delta][$field_name]);
+ $cells[] = array(
+ 'data' => $item,
+ 'class' => $element[$delta]['#attributes']['class'] .' content-multigroup-cell-'. str_replace('_', '-', $field_name),
+ );
+ if (!empty($item)) {
+ $empty = FALSE;
+ }
+ }
+ // Get the row only if there is at least one non-empty field.
+ if (!$empty) {
+ $rows[] = $cells;
+ }
+ }
+ return count($rows) ? theme('table', $headers, $rows, $element['#attributes']) : '';
+}
diff -Nurp ../cck.orig/modules/content_multigroup/panels/content_types/content_multigroup.inc ./modules/content_multigroup/panels/content_types/content_multigroup.inc
--- ../cck.orig/modules/content_multigroup/panels/content_types/content_multigroup.inc 1969-12-31 18:00:00.000000000 -0600
+++ ./modules/content_multigroup/panels/content_types/content_multigroup.inc 2010-05-05 09:39:21.000000000 -0500
@@ -0,0 +1,175 @@
+ t('Content multigroup'),
+ 'defaults' => array('label' => 'hidden', 'format' => 'simple', 'subgroup' => 'fieldset', 'empty' => ''),
+ );
+}
+
+/**
+ * Return all multigroup content types available.
+ */
+function content_multigroup_content_multigroup_content_type_content_types() {
+ // This will hold all the individual multigroup content types.
+ $types = array();
+
+ // The outer loop goes through each node type with groups.
+ foreach (fieldgroup_groups() as $node_type_groups) {
+ // The inner loop gives us each fieldgroup on each node type with groups.
+ foreach ($node_type_groups as $group) {
+ // Skip field groups that are not of multigroup type.
+ if ($group['group_type'] != 'multigroup') {
+ continue;
+ }
+
+ // Name the content type a combination of fieldgroup and node type names.
+ $content_type_name = $group['type_name'] . ':' . $group['group_name'];
+
+ // Assemble the information about the content type.
+ $info = array(
+ 'category' => t('Node'),
+ 'icon' => 'icon_cck_field_group.png',
+ 'title' => t('Multigroup: @group in @type', array(
+ '@group' => t($group['label']),
+ '@type' => node_get_types('name', $group['type_name']),
+ )),
+ 'description' => t('All fields from this field group on the referenced node.'),
+ 'required context' => new ctools_context_required(t('Node'), 'node', array('type' => array($group['type_name']))),
+ );
+
+ $types[$content_type_name] = $info;
+ }
+ }
+
+ return $types;
+}
+
+/**
+ * Output function for the 'multigroup' content type.
+ */
+function content_multigroup_content_multigroup_content_type_render($subtype, $conf, $panel_args, $context) {
+ if (!isset($context->data)) {
+ return;
+ }
+ $node = drupal_clone($context->data);
+
+ // Extract the node type and fieldgroup name from the subtype.
+ list($node_type, $group_name) = explode(':', $subtype, 2);
+
+ // Get a list of all fieldgroups for this node type.
+ $groups = fieldgroup_groups($node_type);
+
+ if (!isset($groups[$group_name])) {
+ return;
+ }
+ $group = $groups[$group_name];
+
+ // Render the field group.
+ $node->build_mode = NODE_BUILD_NORMAL;
+ $group['settings']['display']['label'] = $conf['label'] == 'normal' || !empty($conf['override_title']) ? 'hidden' : $conf['label'];
+ $group['settings']['display']['full']['format'] = $conf['format'];
+ $group['settings']['display']['full']['exclude'] = 0;
+ $group['settings']['multigroup']['subgroup']['full']['format'] = $conf['subgroup'];
+ $group['settings']['multigroup']['subgroup']['full']['exclude'] = 0;
+ $output = fieldgroup_view_group($group, $node);
+
+ $block = new stdClass();
+ if ($conf['label'] == 'normal') {
+ $block->title = t($group['label']);
+ }
+ $block->content = !empty($output) ? $output : $conf['empty'];
+ return $block;
+}
+
+/**
+ * Returns a settings form for the custom type.
+ */
+function content_multigroup_content_multigroup_content_type_edit_form(&$form, &$form_state) {
+ $conf = $form_state['conf'];
+
+ $label_options = array(
+ 'normal' => t('Block title'),
+ 'above' => t('Above'),
+ );
+ $form['label'] = array(
+ '#type' => 'select',
+ '#title' => t('Multigroup label'),
+ '#default_value' => !empty($conf['label']) && isset($label_options[$conf['label']]) ? $conf['label'] : 'hidden',
+ '#options' => $label_options,
+ '#description' => t('Configure how the field group label is going to be displayed. This option takes no effect when "Override title" option is enabled, the specified block title is displayed instead.'),
+ );
+
+ $format_options = array(
+ 'simple' => t('Simple'),
+ 'fieldset' => t('Fieldset'),
+ 'fieldset_collapsible' => t('Fieldset - Collapsible'),
+ 'fieldset_collapsed' => t('Fieldset - Collapsed'),
+ );
+ $form['format'] = array(
+ '#type' => 'select',
+ '#title' => t('Multigroup format'),
+ '#default_value' => !empty($conf['format']) && isset($format_options[$conf['format']]) ? $conf['format'] : 'simple',
+ '#options' => $format_options,
+ '#description' => t('This option allows you to configure the field group format.'),
+ );
+
+ $subgroup_options = array(
+ 'simple' => t('Simple'),
+ 'fieldset' => t('Fieldset'),
+ 'fieldset_collapsible' => t('Fieldset - collapsible'),
+ 'fieldset_collapsed' => t('Fieldset - collapsed'),
+ 'hr' => t('Horizontal line'),
+ 'table-single' => t('Table - Single column'),
+ 'table-multiple' => t('Table - Multiple columns'),
+ );
+ $form['subgroup'] = array(
+ '#type' => 'select',
+ '#title' => t('Subgroup format'),
+ '#default_value' => !empty($conf['subgroup']) && isset($subgroup_options[$conf['subgroup']]) ? $conf['subgroup'] : 'fieldset',
+ '#options' => $subgroup_options,
+ '#description' => t('This option allows you to configure the format of the subgroups in the multigroup.'),
+ );
+
+ $form['empty'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Empty text'),
+ '#description' => t('Text to display if group has no data. Note that title will not display unless overridden.'),
+ '#rows' => 5,
+ '#default_value' => $conf['empty'],
+ );
+}
+
+function content_multigroup_content_multigroup_content_type_edit_form_submit(&$form, &$form_state) {
+ // Copy everything from our defaults.
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+}
+
+/**
+ * Admin title for multigroup content type.
+ */
+function content_multigroup_content_multigroup_content_type_admin_title($subtype, $conf, $context) {
+ // Extract the node type and fieldgroup name from the subtype.
+ list($node_type, $group_name) = explode(':', $subtype, 2);
+
+ // Get information about this field group for this node type.
+ $groups = fieldgroup_groups($node_type);
+ $group = $groups[$group_name];
+
+ return t('"@s" multigroup: @group in @type', array(
+ '@s' => $context->identifier,
+ '@group' => t($group['label']),
+ '@type' => node_get_types('name', $node_type),
+ ));
+}
Files ../cck.orig/modules/content_multigroup/panels/content_types/icon_cck_field_group.png and ./modules/content_multigroup/panels/content_types/icon_cck_field_group.png differ
diff -Nurp ../cck.orig/modules/content_multigroup/panels/content_types/translations/de.po ./modules/content_multigroup/panels/content_types/translations/de.po
--- ../cck.orig/modules/content_multigroup/panels/content_types/translations/de.po 1969-12-31 18:00:00.000000000 -0600
+++ ./modules/content_multigroup/panels/content_types/translations/de.po 2010-05-05 09:39:21.000000000 -0500
@@ -0,0 +1,51 @@
+# $Id$
+#
+# LANGUAGE translation of Drupal (modules-content_multigroup-panels-content_types)
+# Copyright 2009 NAME
+# Generated from file: content_multigroup.inc,v 1.1.2.3 2009/11/01 10:44:16 markuspetrux
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"POT-Creation-Date: 2009-12-06 15:33+0100\n"
+"PO-Revision-Date: 2009-12-06 22:17+0100\n"
+"Last-Translator: Thomas Zahreddin \n"
+"Language-Team: German \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Language: German\n"
+"X-Poedit-Country: German\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+
+#: modules/content_multigroup/panels/content_types/content_multigroup.inc:42
+#, fuzzy
+msgid "Multigroup: @group in @type"
+msgstr "Vielfachgruppe: @group in @type"
+
+#: modules/content_multigroup/panels/content_types/content_multigroup.inc:106
+#, fuzzy
+msgid "Multigroup label"
+msgstr "Bezeichnung der Vielfachgruppe"
+
+#: modules/content_multigroup/panels/content_types/content_multigroup.inc:120
+#, fuzzy
+msgid "Multigroup format"
+msgstr "Format für Vielfachgruppe"
+
+#: modules/content_multigroup/panels/content_types/content_multigroup.inc:137
+#, fuzzy
+msgid "Subgroup format"
+msgstr "Format für Untergruppe"
+
+#: modules/content_multigroup/panels/content_types/content_multigroup.inc:140
+#, fuzzy
+msgid "This option allows you to configure the format of the subgroups in the multigroup."
+msgstr "Konfigurieren des Formats für eine Subgruppe innerhalb einer Vielfachgruppe."
+
+#: modules/content_multigroup/panels/content_types/content_multigroup.inc:170
+#, fuzzy
+msgid "\"@s\" multigroup: @group in @type"
+msgstr "\"@s\" Vielfachgruppe: @group in @type"
+
diff -Nurp ../cck.orig/modules/content_multigroup/translations/modules-content_multigroup.de.po ./modules/content_multigroup/translations/modules-content_multigroup.de.po
--- ../cck.orig/modules/content_multigroup/translations/modules-content_multigroup.de.po 1969-12-31 18:00:00.000000000 -0600
+++ ./modules/content_multigroup/translations/modules-content_multigroup.de.po 2010-05-05 09:39:21.000000000 -0500
@@ -0,0 +1,128 @@
+# $Id$
+#
+# LANGUAGE translation of Drupal (modules-content_multigroup)
+# Copyright 2009 NAME
+# Generated from files:
+# content_multigroup.admin.inc,v 1.1.2.5 2009/11/02 19:22:37 markuspetrux
+# content_multigroup.node_form.inc,v 1.1.2.12 2009/11/03 19:16:25 markuspetrux
+# content_multigroup.module,v 1.1.4.6 2009/11/02 19:22:37 markuspetrux
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"POT-Creation-Date: 2009-12-06 15:33+0100\n"
+"PO-Revision-Date: 2009-12-06 22:25+0100\n"
+"Last-Translator: Thomas Zahreddin \n"
+"Language-Team: German \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Language: German\n"
+"X-Poedit-Country: Germany\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+
+#: modules/content_multigroup/content_multigroup.admin.inc:90
+msgid "The field %field has been updated to use %multiple values, to match the multiple value setting of the Multigroup %group."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.admin.inc:129
+msgid "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."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.admin.inc:153
+msgid "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."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.admin.inc:168
+msgid "You are moving the field %field into a Multigroup."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.admin.inc:201
+msgid "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."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.admin.inc:215
+msgid "You are moving the field %field out of a Multigroup."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.admin.inc:250
+msgid "The widget type cannot be changed because the field %field already has data created and this widget stores data differently in a Standard group than in a Multigroup. Allowing this change could result in the loss of data."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.admin.inc:313
+msgid "[Subgroup format]"
+msgstr "[Subgroup format]"
+
+#: modules/content_multigroup/content_multigroup.admin.inc:399
+msgid "Multigroup settings"
+msgstr "Multigroup Einstellungen"
+
+#: modules/content_multigroup/content_multigroup.admin.inc:414
+msgid "Multiple columns"
+msgstr "Mehrere Spalten"
+
+#: modules/content_multigroup/content_multigroup.admin.inc:416
+msgid "Enable this option to render each field on a separate column on the node edit form."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.admin.inc:423
+msgid "Enable this option to require a minimum of one collection of fields in this Multigroup."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.admin.inc:426
+msgid "Number of times to repeat the collection of Multigroup fields."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.admin.inc:427
+msgid "'Unlimited' will provide an 'Add more' button so the users can add items as many times as they like."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.admin.inc:428
+msgid "All fields in this group will automatically be set to allow this number of values."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.admin.inc:433
+msgid "Number of repeats"
+msgstr "Anzahl von Wiederholungen"
+
+#: modules/content_multigroup/content_multigroup.admin.inc:441
+msgid "Labels"
+msgstr "Bezeichner"
+
+#: modules/content_multigroup/content_multigroup.admin.inc:442
+msgid "Labels for each subgroup of fields. Labels can be hidden or shown in various contexts using the 'Display fields' screen."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.admin.inc:450
+msgid "Subgroup %number label"
+msgstr "Untergruppe %number Bezeichner"
+
+#: modules/content_multigroup/content_multigroup.admin.inc:477
+msgid "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."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.node_form.inc:84
+msgid "This group requires one collection of fields minimum."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.node_form.inc:385
+msgid "!name field is required in group @group."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.node_form.inc:401
+msgid "Group @name requires one collection of fields minimum."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.node_form.inc:648
+msgid "Add more values"
+msgstr "weitere Werte hinzufügen"
+
+#: modules/content_multigroup/content_multigroup.module:12
+msgid "The fields in a Standard group are independent of each other and each can have either single or multiple values. The fields in a Multigroup are treated as a repeating collection of single value fields."
+msgstr ""
+
+#: modules/content_multigroup/content_multigroup.module:99
+msgid "Multigroup"
+msgstr ""
+
diff -Nurp ../cck.orig/modules/content_multigroup/views/content_multigroup.views.inc ./modules/content_multigroup/views/content_multigroup.views.inc
--- ../cck.orig/modules/content_multigroup/views/content_multigroup.views.inc 1969-12-31 18:00:00.000000000 -0600
+++ ./modules/content_multigroup/views/content_multigroup.views.inc 2010-05-05 09:39:21.000000000 -0500
@@ -0,0 +1,95 @@
+ $groups) {
+ $type_label = node_get_types('name', $type_name);
+
+ foreach ($groups as $group_name => $group) {
+ // Let's focus on multigroups that really accept multiple values.
+ if ($group['group_type'] == 'multigroup' && !empty($group['settings']['multigroup']['multiple'])) {
+
+ // Scan all fields in this multigroup.
+ $field_labels = array();
+ foreach (array_keys($group['fields']) as $field_name) {
+ // Load information about the field for this particular content type.
+ $field = content_fields($field_name, $type_name);
+
+ // Get the Views table alias.
+ $table_alias = content_views_tablename($field);
+
+ // Discard this field if not already exposed to Views.
+ if (isset($data[$table_alias])) {
+ $field_labels[$field_name] = t($field['widget']['label']);
+ }
+ }
+
+ if (!empty($field_labels)) {
+ // Build the name for this filter.
+ // The scope of field groups is the content type itself. You can
+ // have more than one group with the same name but different fields
+ // in different content types. Therefore, we need the type name and
+ // multigroup name.
+ $db_field = 'multigroup_'. $type_name .'_'. $group_name;
+
+ // Build the labels for the filter.
+ $label_truncated = truncate_utf8(t($group['label']), 10, TRUE);
+ $title = t('@group_label multigroup in @type_label', array(
+ '@group_label' => t($group['label']),
+ '@type_label' => $type_label,
+ ));
+ $title_short = t('@label-truncated in @type_label', array(
+ '@label-truncated' => $label_truncated,
+ '@type_label' => $type_label,
+ ));
+ $help_text = t('Synchronize multiple values for fields in @group_label multigroup, @type_label type. Fields in this group: @fields.', array(
+ '@group_label' => t($group['label']),
+ '@type_label' => $type_label,
+ '@fields' => implode(', ', $field_labels),
+ ));
+
+ // Attach the new filter to the node table.
+ $data['node'][$db_field] = array(
+ 'group' => t('Content multigroup'),
+ 'title' => $title,
+ 'title short' => $title_short,
+ 'help' => $help_text,
+ 'filter' => array(
+ 'field' => $db_field,
+ 'table' => 'node',
+ 'handler' => 'content_multigroup_handler_filter',
+ 'content_type_name' => $type_name,
+ 'content_group_name' => $group_name,
+ 'allow empty' => TRUE,
+ ),
+ );
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Implementation of hook_views_handlers().
+ */
+function content_multigroup_views_handlers() {
+ return array(
+ 'info' => array(
+ 'path' => drupal_get_path('module', 'content_multigroup') . '/views/handlers',
+ ),
+ 'handlers' => array(
+ 'content_multigroup_handler_filter' => array(
+ 'parent' => 'views_handler_filter',
+ ),
+ ),
+ );
+}
diff -Nurp ../cck.orig/modules/content_multigroup/views/handlers/content_multigroup_handler_filter.inc ./modules/content_multigroup/views/handlers/content_multigroup_handler_filter.inc
--- ../cck.orig/modules/content_multigroup/views/handlers/content_multigroup_handler_filter.inc 1969-12-31 18:00:00.000000000 -0600
+++ ./modules/content_multigroup/views/handlers/content_multigroup_handler_filter.inc 2010-05-05 09:39:21.000000000 -0500
@@ -0,0 +1,100 @@
+definition['content_type_name']);
+ return $groups[$this->definition['content_group_name']];
+ }
+
+ /**
+ * Get information about the fields in this multigroup.
+ */
+ function _get_multigroup_fields() {
+ if (!isset($this->content_multigroup_fields)) {
+ $group = $this->_get_multigroup();
+ $this->content_multigroup_fields = array();
+ foreach (array_keys($group['fields']) as $field_name) {
+ $field = content_fields($field_name, $this->definition['content_type_name']);
+ $table_alias = content_views_tablename($field);
+ $this->content_multigroup_fields[$table_alias] = $field;
+ }
+ }
+ return $this->content_multigroup_fields;
+ }
+
+ /**
+ * Define default value for master field.
+ */
+ function options(&$options) {
+ $multigroup_fields = $this->_get_multigroup_fields();
+ // Find the first required field.
+ foreach ($multigroup_fields as $table_alias => $field) {
+ if ($field['required']) {
+ $options['content_multigroup_master_field'] = $table_alias;
+ break;
+ }
+ }
+ // Default to first field in the multigroup.
+ if (empty($options['content_multigroup_master_field'])) {
+ $options['content_multigroup_master_field'] = current(array_keys($multigroup_fields));
+ }
+ }
+
+ /**
+ * Options from to ask the user for a master field.
+ */
+ function options_form(&$form, &$form_state) {
+ $group = $this->_get_multigroup();
+ $options = array();
+ foreach ($this->_get_multigroup_fields() as $table_alias => $field) {
+ $options[$table_alias] = t($field['widget']['label']);
+ }
+ $form['content_multigroup_master_field'] = array(
+ '#title' => t('Available fields in @group_label multigroup', array('@group_label' => t($group['label']))),
+ '#type' => 'radios',
+ '#options' => $options,
+ '#default_value' => $this->options['content_multigroup_master_field'],
+ '#description' => t('Select the field in this multigroup that will be used to build the primary join with the content table.'),
+ );
+ }
+
+ /**
+ * Add joins to the query to synchronize the fields in this multigroup.
+ */
+ function query() {
+ // Create the join between the master field table and the node table.
+ $base_alias = $this->query->ensure_table($this->options['content_multigroup_master_field'], $this->relationship);
+
+ // Now we want to join the master field table with all other tables
+ // related to fields in the same multigroup, but adding the delta
+ // key to the join condition. This is what allows us to keep delta
+ // values in sync for all fields in the same multigroup.
+ foreach ($this->_get_multigroup_fields() as $table_alias => $field) {
+ if ($table_alias != $this->options['content_multigroup_master_field']) {
+ $alias = $this->query->ensure_table($table_alias, $this->relationship);
+ $this->query->table_queue[$alias]['join']->extra = $base_alias .'.delta = '. $alias .'.delta';
+ }
+ }
+ }
+}
diff -Nurp ../cck.orig/modules/content_multigroup/views/handlers/translations/modules-content_multigroup-views-handlers.de.po ./modules/content_multigroup/views/handlers/translations/modules-content_multigroup-views-handlers.de.po
--- ../cck.orig/modules/content_multigroup/views/handlers/translations/modules-content_multigroup-views-handlers.de.po 1969-12-31 18:00:00.000000000 -0600
+++ ./modules/content_multigroup/views/handlers/translations/modules-content_multigroup-views-handlers.de.po 2010-05-05 09:39:21.000000000 -0500
@@ -0,0 +1,33 @@
+# $Id$
+#
+# LANGUAGE translation of Drupal (modules-content_multigroup-views-handlers)
+# Copyright 2009 NAME
+# Generated from file: content_multigroup_handler_filter.inc,v 1.1.2.2 2009/10/31 20:31:09 markuspetrux
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"POT-Creation-Date: 2009-12-06 15:33+0100\n"
+"PO-Revision-Date: 2009-12-06 22:28+0100\n"
+"Last-Translator: Thomas Zahreddin \n"
+"Language-Team: German \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Language: German\n"
+"X-Poedit-Country: Germany\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+
+#: modules/content_multigroup/views/handlers/content_multigroup_handler_filter.inc:19
+msgid "+ delta"
+msgstr "+ delta"
+
+#: modules/content_multigroup/views/handlers/content_multigroup_handler_filter.inc:74
+msgid "Available fields in @group_label multigroup"
+msgstr ""
+
+#: modules/content_multigroup/views/handlers/content_multigroup_handler_filter.inc:78
+msgid "Select the field in this multigroup that will be used to build the primary join with the content table."
+msgstr ""
+
diff -Nurp ../cck.orig/modules/content_multigroup/views/translations/modules-content_multigroup-views.de.po ./modules/content_multigroup/views/translations/modules-content_multigroup-views.de.po
--- ../cck.orig/modules/content_multigroup/views/translations/modules-content_multigroup-views.de.po 1969-12-31 18:00:00.000000000 -0600
+++ ./modules/content_multigroup/views/translations/modules-content_multigroup-views.de.po 2010-05-05 09:39:21.000000000 -0500
@@ -0,0 +1,33 @@
+# $Id$
+#
+# LANGUAGE translation of Drupal (modules-content_multigroup-views)
+# Copyright 2009 NAME
+# Generated from file: content_multigroup.views.inc,v 1.1.2.2 2009/08/10 03:53:05 markuspetrux
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"POT-Creation-Date: 2009-12-06 15:33+0100\n"
+"PO-Revision-Date: 2009-12-06 22:29+0100\n"
+"Last-Translator: Thomas Zahreddin \n"
+"Language-Team: German\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Language: German\n"
+"X-Poedit-Country: Germany\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+
+#: modules/content_multigroup/views/content_multigroup.views.inc:46
+msgid "@group_label multigroup in @type_label"
+msgstr "@group_label multigroup in @type_label"
+
+#: modules/content_multigroup/views/content_multigroup.views.inc:50
+msgid "@label-truncated in @type_label"
+msgstr "@label-truncated in @type_label"
+
+#: modules/content_multigroup/views/content_multigroup.views.inc:54
+msgid "Synchronize multiple values for fields in @group_label multigroup, @type_label type. Fields in this group: @fields."
+msgstr ""
+