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'] = '
'; + $form_element['#suffix'] = '
'; + $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 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'] = '
'. (isset($group_form[$delta]['#prefix']) ? $group_form[$delta]['#prefix'] : ''); + $group_form[$delta]['#suffix'] = (isset($group_form[$delta]['#suffix']) ? $group_form[$delta]['#suffix'] : '') .'
'; + // Prevent duplicate wrapper. + unset($group_form['#prefix'], $group_form['#suffix']); + // We're in the AHAH handler, so the fieldset was expanded. + $group_form['#collapsed'] = FALSE; + + // 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; + + // Using drupal_json() breaks filefield's file upload, because the jQuery + // Form plugin handles file uploads in a way that is not compatible with + // 'text/javascript' response type. + $GLOBALS['devel_shutdown'] = FALSE; + print drupal_to_js(array('status' => TRUE, 'data' => $output)); + exit; +} + +/** + * Theme an individual form element. + * + * Combine multiple values into a table with drag-n-drop reordering. + */ +function theme_content_multigroup_node_form($element) { + $group_name = $element['#group_name']; + $groups = fieldgroup_groups($element['#type_name']); + $group = $groups[$group_name]; + $group_multiple = $group['settings']['multigroup']['multiple']; + $group_fields = $element['#group_fields']; + + $table_id = $element['#group_name'] .'_values'; + $table_class = 'content-multiple-table'; + $order_class = $element['#group_name'] .'-delta-order'; + $subgroup_settings = isset($group['settings']['multigroup']['subgroup']) ? $group['settings']['multigroup']['subgroup'] : array(); + $show_label = isset($subgroup_settings['label']) ? $subgroup_settings['label'] : 'above'; + $subgroup_labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array(); + $multiple_columns = isset($group['settings']['multigroup']['multiple-columns']) ? $group['settings']['multigroup']['multiple-columns'] : 0; + + $headers = array(); + if ($group_multiple >= 1) { + $headers[] = array('data' => ''); + } + if ($multiple_columns) { + foreach ($group_fields as $field_name => $field) { + $required = !empty($field['required']) ? ' *' : ''; + $headers[] = array( + 'data' => check_plain(t($field['widget']['label'])) . $required, + 'class' => 'content-multigroup-cell-'. str_replace('_', '-', $field_name), + ); + } + $table_class .= ' content-multigroup-edit-table-multiple-columns'; + } + else { + if ($group_multiple >= 1) { + $headers[0]['colspan'] = 2; + } + $table_class .= ' content-multigroup-edit-table-single-column'; + } + if ($group_multiple >= 1) { + $headers[] = array('data' => t('Order'), 'class' => 'content-multiple-weight-header'); + if ($group_multiple == 1) { + $headers[] = array('data' => ''. t('Remove') .'', 'class' => 'content-multiple-remove-header'); + } + } + $rows = array(); + + $i = 0; + foreach (element_children($element) as $delta => $key) { + if (is_numeric($key)) { + $cells = array(); + $label = ($show_label == 'above' && !empty($subgroup_labels[$i]) ? theme('content_multigroup_node_label', check_plain(t($subgroup_labels[$i]))) : ''); + $element[$key]['_weight']['#attributes']['class'] = $order_class; + if ($group_multiple >= 1) { + $cells[] = array('data' => '', 'class' => 'content-multiple-drag'); + $delta_element = drupal_render($element[$key]['_weight']); + if ($group_multiple == 1) { + $remove_element = drupal_render($element[$key]['_remove']); + } + } + else { + $element[$key]['_weight']['#type'] = 'hidden'; + } + if ($multiple_columns) { + foreach ($group_fields as $field_name => $field) { + $cell = array( + 'data' => (isset($element[$key][$field_name]) ? drupal_render($element[$key][$field_name]) : ''), + 'class' => 'content-multigroup-cell-'. str_replace('_', '-', $field_name), + ); + if (!empty($cell['data']) && !empty($element[$key][$field_name]['#description'])) { + $cell['title'] = $element[$key][$field_name]['#description']; + } + $cells[] = $cell; + } + } + else { + $cells[] = $label . drupal_render($element[$key]); + } + if ($group_multiple >= 1) { + $row_class = 'draggable'; + $cells[] = array('data' => $delta_element, 'class' => 'delta-order'); + if ($group_multiple == 1) { + if (!empty($element[$key]['_remove']['#value'])) { + $row_class .= ' content-multiple-removed-row'; + } + $cells[] = array('data' => $remove_element, 'class' => 'content-multiple-remove-cell'); + } + $rows[] = array('data' => $cells, 'class' => $row_class); + } + else { + $rows[] = array('data' => $cells); + } + } + $i++; + } + + drupal_add_css(drupal_get_path('module', 'content_multigroup') .'/content_multigroup.css'); + $output = theme('table', $headers, $rows, array('id' => $table_id, 'class' => $table_class)); + $output .= drupal_render($element[$group_name .'_add_more']); + + // Enable drag-n-drop only if the group really allows multiple values. + if ($group_multiple >= 1) { + drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class); + drupal_add_js(drupal_get_path('module', 'content') .'/js/content.node_form.js'); + } + + return $output; +} + +/** + * Theme the sub group label in the node form. + */ +function theme_content_multigroup_node_label($text) { + return !empty($text) ? '

'. $text .'

' : ''; +} diff -Nurp ../cck.orig/modules/content_multigroup/content_multigroup.node_view.inc ./modules/content_multigroup/content_multigroup.node_view.inc --- ../cck.orig/modules/content_multigroup/content_multigroup.node_view.inc 1969-12-31 18:00:00.000000000 -0600 +++ ./modules/content_multigroup/content_multigroup.node_view.inc 2010-05-05 09:39:21.000000000 -0500 @@ -0,0 +1,237 @@ + $field) { + if (isset($group['fields'][$field_name]) && isset($element[$field_name])) { + if (!isset($element[$field_name]['#access']) || $element[$field_name]['#access']) { + $group_fields[$field_name] = $field; + } + } + } + + switch ($group_multiple) { + case 0: + $group_deltas = array(0); + break; + + case 1: + // Compute unique deltas from all deltas used by fields in this multigroup. + $group_deltas = array(); + foreach (array_keys($group_fields) as $field_name) { + if (is_array($node->content[$field_name]['field']['items'])) { + foreach (array_keys($node->content[$field_name]['field']['items']) as $delta) { + $group_deltas[$delta] = $delta; + } + } + } + sort($group_deltas); + break; + + default: + $group_deltas = range(0, $group_multiple - 1); + break; + } + + foreach ($group_deltas as $index => $delta) { + $element[$delta] = array( + '#title' => ($show_label == 'above' && !empty($subgroup_labels[$index]) ? check_plain(t($subgroup_labels[$index])) : ''), + '#attributes' => array('class' => 'content-multigroup-wrapper content-multigroup-'. $index), + '#weight' => $delta, + ); + + // Create a pseudo node that only has the value we want in this group and + // pass it to the formatter. + // Default implementation of content-field.tpl.php uses a different CSS + // class for inline labels when delta is zero, but this is not needed in + // the context of multigroup, so we place the field into index 1 of the + // item list. Note that CSS class "field-label-inline" is overridden in the + // multigroup stylesheet because here labels should always be visible. + foreach (array_keys($group_fields) as $field_name) { + if (isset($node->content[$field_name])) { + $node_copy->content[$field_name]['field']['items'] = array( + 1 => 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 ($subgroup_format) { + case 'simple': + $element['#attributes']['class'] = $group_class; + $element[$delta]['#theme'] = 'content_multigroup_display_simple'; + $element[$delta]['#fields'] = $group_fields; + break; + case 'fieldset_collapsed': + $element[$delta]['#collapsed'] = TRUE; + case 'fieldset_collapsible': + $element[$delta]['#collapsible'] = TRUE; + case 'fieldset': + $element['#attributes']['class'] = $group_class; + $element[$delta]['#type'] = 'content_multigroup_display_fieldset'; + $element[$delta]['#fields'] = $group_fields; + break; + case 'hr': + $element['#attributes']['class'] = $group_class; + $element[$delta]['#theme'] = 'content_multigroup_display_hr'; + $element[$delta]['#fields'] = $group_fields; + break; + case 'table-single': + $element['#theme'] = 'content_multigroup_display_table_single'; + $element['#attributes']['class'] = $group_class .' content-multigroup-display-table-single-column'; + $element['#fields'] = $group_fields; + break; + case 'table-multiple': + $element['#theme'] = 'content_multigroup_display_table_multiple'; + $element['#attributes']['class'] = $group_class .' content-multigroup-display-table-multiple-columns'; + $element['#fields'] = $group_fields; + break; + } + } + + // Unset the original group field values now that we've moved them. + foreach (array_keys($group_fields) as $field_name) { + if (isset($element[$field_name])) { + unset($element[$field_name]); + } + } +} + +/** + * Theme a subgroup of fields in 'simple' format. + * + * No output is generated if all fields are empty. + */ +function theme_content_multigroup_display_simple($element) { + $children = $output = ''; + foreach (element_children($element) as $key) { + $children .= drupal_render($element[$key]); + } + if (!empty($children)) { + $output .= ''; + if (!empty($element['#title'])) { + $output .= ''; + } + $output .= $children .''; + } + return $output; +} + +/** + * Theme a subgroup of fields in 'fieldset' format. + * + * No output is generated if all fields are empty. + */ +function theme_content_multigroup_display_fieldset($element) { + if (empty($element['#children']) && empty($element['#value'])) { + return ''; + } + return theme('fieldset', $element); +} + +/** + * Theme a subgroup of fields in 'hr' format. + * + * No output is generated if all fields are empty. + */ +function theme_content_multigroup_display_hr($element) { + $children = $output = ''; + foreach (element_children($element) as $key) { + $children .= drupal_render($element[$key]); + } + if (!empty($children)) { + $output .= '
'; + if (!empty($element['#title'])) { + $output .= ''; + } + $output .= $children .''; + } + 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 "" +