diff -rupN modules/content_multigroup/content_multigroup.css modules/content_multigroup/content_multigroup.css --- modules/content_multigroup/content_multigroup.css 2008-10-14 17:16:50.000000000 +0200 +++ modules/content_multigroup/content_multigroup.css 2008-12-18 20:30:37.000000000 +0100 @@ -2,8 +2,29 @@ label.content-multigroup { font-weight:bold; -} +} -/* Not styled by default, but available to style */ +/* Not styled by default, but available to style. */ hr.content-multigroup { -} \ No newline at end of file +} + +/* Inline field labels visible within the content of multigroups. */ +.content-multigroup-wrapper .field .field-label-inline { + visibility:visible; +} + +/* Hide field labels when using table display mode. */ +.content-multigroup-display-table .field .field-label, +.content-multigroup-display-table .field .field-label-inline, +.content-multigroup-display-table .field .field-label-inline-first { + display:none; +} + +/** + * 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; +} diff -rupN modules/content_multigroup/content_multigroup.module modules/content_multigroup/content_multigroup.module --- modules/content_multigroup/content_multigroup.module 2008-10-22 13:02:41.000000000 +0200 +++ modules/content_multigroup/content_multigroup.module 2008-12-18 20:35:21.000000000 +0100 @@ -37,6 +37,9 @@ function content_multigroup_theme() { 'content_multigroup_display_simple' => array( 'arguments' => array('element' => NULL), ), + 'content_multigroup_display_fieldset' => array( + 'arguments' => array('element' => NULL), + ), 'content_multigroup_display_hr' => array( 'arguments' => array('element' => NULL), ), @@ -47,6 +50,15 @@ function content_multigroup_theme() { } /** + * 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() { @@ -61,7 +73,7 @@ function content_multigroup_fieldgroup_d module_load_include('inc', 'content', 'includes/content.admin'); $settings = array('multigroup' => array('multiple' => 1)); foreach (array_keys(content_build_modes()) as $key) { - $settings['multigroup']['display_settings'][$key]['format'] = 'fieldset'; + $settings['display'][$key]['format'] = 'fieldset'; } return $settings; } @@ -108,12 +120,12 @@ function content_multigroup_form_alter(& content_multigroup_field_overview_form($form, $form_state); $form['#validate'][] = 'content_multigroup_field_overview_form_validate'; } - elseif ($form_id == 'content_display_overview_form') { + elseif ($form_id == 'content_display_overview_form' && !empty($form['#groups'])) { content_multigroup_display_overview_form($form, $form_state, $form_id); $form['#submit'] = array_merge(array('content_multigroup_display_overview_form_submit'), $form['#submit']); } elseif ($form_id == 'fieldgroup_group_edit_form') { - return content_multigroup_group_edit_form($form, $form_state, $form_id); + content_multigroup_group_edit_form($form, $form_state, $form_id); } } @@ -180,39 +192,37 @@ function content_multigroup_field_overvi } } - if (!empty($fields)) { - foreach ($fields as $field_name => $field) { - $new_group = $form_values[$field_name]['parent']; - $old_group = $form_values[$field_name]['prev_parent']; - if (!empty($new_group) && isset($groups[$new_group]) && $groups[$new_group]['group_type'] == 'multigroup') { - $allowed_in = 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); - $multiple = $groups[$new_group]['settings']['multigroup']['multiple']; - $multiple_values = content_multigroup_multiple_values(); - $field = $content_type['fields'][$field_name]; - $field['multiple'] = $multiple; - $field = content_field_instance_collapse($field); - content_field_instance_update($field); - drupal_set_message(t('The field %field has been updated to use %multiple values, to match the multiple value setting of the Multigroup %group.', array( - '%field' => $field['label'], '%multiple' => $multiple_values[$multiple], '%group' => $groups[$new_group]['label']))); - } + 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']); } - 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']); + 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); + 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']); } } } @@ -230,12 +240,12 @@ function content_multigroup_allowed_in($ // We can't allow fields with more multiple values than the group has // to be moved into it. $max_existing = content_max_delta($field['field_name']); - $group_max = $group['settings']['multigroup']['multiple']; + $group_multiple = $group['settings']['multigroup']['multiple']; $multiple_values = content_multigroup_multiple_values(); - if ($group_max != 1 && $max_existing > $group_max) { + 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_max])) + '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])) ); } @@ -254,7 +264,7 @@ function content_multigroup_allowed_in($ '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( @@ -295,14 +305,14 @@ function content_multigroup_allowed_out( $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', - ); + '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( @@ -336,14 +346,17 @@ function content_multigroup_display_over $contexts_selector = $form['#contexts']; // Gather type information. - $type = content_types($type_name); - $field_types = _content_field_types(); - $fields = $type['fields']; + $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; + } $groups = $group_options = array(); if (module_exists('fieldgroup')) { - $groups = fieldgroup_groups($type['type']); - $group_options = _fieldgroup_groups_label($type['type']); + $groups = fieldgroup_groups($type_name); + $group_options = _fieldgroup_groups_label($type_name); } $contexts = content_build_modes($contexts_selector); @@ -353,41 +366,41 @@ function content_multigroup_display_over 'hidden' => t(''), ); $options = array( - 'none' => t('none'), + 'simple' => t('Simple'), 'fieldset' => t('Fieldset'), 'hr' => t('Horizontal line'), - //'table' => t('Table'), // TODO add this later - 'hidden' => t(''), + 'table' => t('Table'), ); - foreach ($groups as $name => $group) { + foreach ($groups as $group_name => $group) { if ($group['group_type'] != 'multigroup') { continue; } - $defaults = $group['settings']['multigroup']['display_settings']; + $subgroup_settings = isset($group['settings']['multigroup']['subgroup']) ? $group['settings']['multigroup']['subgroup'] : array(); - $form_name = $name .'_subgroup'; - $form['#fields'] = array_merge(array($form_name), $form['#fields']); - $form[$form_name] = 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' => $name), + 'parent' => array('#type' => 'value', '#value' => $group_name), + 'subgroup' => array('#type' => 'value', '#value' => 1), ); if ($contexts_selector == 'basic') { - $form[$form_name]['label'] = array( + $form[$subgroup_name]['label'] = array( '#type' => 'select', '#options' => $label_options, - '#default_value' => isset($defaults['label']) ? $defaults['label'] : 'above', + '#default_value' => isset($subgroup_settings['label']) ? $subgroup_settings['label'] : 'above', ); } foreach ($contexts as $key => $title) { - $form[$form_name][$key]['format'] = array( + $form[$subgroup_name][$key]['format'] = array( '#type' => 'select', '#options' => $options, - '#default_value' => isset($defaults[$key]) ? $defaults[$key] : 'fieldset', + '#default_value' => isset($subgroup_settings[$key]['format']) ? $subgroup_settings[$key]['format'] : 'fieldset', ); + $form[$subgroup_name][$key]['exclude'] = array('#type' => 'value', '#value' => 0); } } - return $form; } /** @@ -397,19 +410,18 @@ function content_multigroup_display_over * tries to use our 'field'. */ function content_multigroup_display_overview_form_submit($form, &$form_state) { - $form_values = $form_state['values']; - // Find any groups we inserted into the display fields form, // save our settings, and remove them from $form_state. - foreach ($form_values as $key => $values) { - if (in_array($key, $form['#fields']) && substr($key, -9) == '_subgroup') { - $group_name = str_replace('_subgroup', '', $key); + foreach ($form_state['values'] as $key => $values) { + if (in_array($key, $form['#fields']) && !empty($values['parent']) && !empty($values['subgroup'])) { + $group_name = $values['parent']; $groups = fieldgroup_groups($form['#type_name']); $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']['display_settings'][$k] = $v; + $group['settings']['multigroup']['subgroup'][$k] = $v; } fieldgroup_save_group($form['#type_name'], $group); @@ -430,7 +442,7 @@ function content_multigroup_group_edit_f $group_name = $form['group_name']['#default_value']; $content_type = content_types($type_name); - $groups = fieldgroup_groups($content_type['type']); + $groups = fieldgroup_groups($type_name); $group = $groups[$group_name]; if ($group['group_type'] != 'multigroup') { @@ -442,25 +454,38 @@ function content_multigroup_group_edit_f $form['group_type'] = array( '#type' => 'hidden', '#value' => $group['group_type'], - ); + ); $form['settings']['multigroup'] = array( '#type' => 'fieldset', - '#title' => t('Other settings'), + '#title' => t('Multigroup settings'), '#collapsed' => FALSE, '#collapsible' => TRUE, ); + $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 repeat it as many times as they like.") . ' '; + $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.'); - $multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1; + $group_multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1; $form['settings']['multigroup']['multiple'] = array( - '#tree' => TRUE, '#type' => 'select', '#title' => t('Number of repeats'), '#options' => content_multigroup_multiple_values(), - '#default_value' => $multiple, + '#default_value' => $group_multiple, '#description' => $description, ); @@ -469,8 +494,8 @@ function content_multigroup_group_edit_f '#title' => t('Labels'), '#description' => t("Labels for each subgroup of fields. Labels can be hidden or shown in various contexts using the 'Display fields' screen."), ); - if ($multiple < 2) { - $multiple = 0; + if ($group_multiple < 2) { + $group_multiple = 0; } for ($i = 0; $i < 10; $i++) { $form['settings']['multigroup']['labels'][$i] = array( @@ -482,7 +507,6 @@ function content_multigroup_group_edit_f $form['#validate'][] = 'content_multigroup_group_edit_form_validate'; $form['#submit'][] = 'content_multigroup_group_edit_form_submit'; - return $form; } /** @@ -523,10 +547,9 @@ function content_multigroup_group_edit_f $content_type = $form['#content_type']; $groups = fieldgroup_groups($content_type['type']); $group = $groups[$form_values['group_name']]; - $multiple = $form_values['settings']['multigroup']['multiple']; - foreach ($group['fields'] as $field_name => $data) { - $field = $content_type['fields'][$field_name]; - $field['multiple'] = $multiple; + $group_fields = array_intersect_key($content_type['fields'], $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); } @@ -541,65 +564,107 @@ function content_multigroup_group_edit_f * d-n-d each collection of fields as a single delta item. */ function content_multigroup_fieldgroup_form(&$form, &$form_state, $form_id, $group) { - if ($group['group_type'] != 'multigroup' || - !empty($form[$group['group_name']]['#access']) || empty($form[$group['group_name']])) { + $group_name = $group['group_name']; + if ($group['group_type'] != 'multigroup' || !empty($form[$group_name]['#access']) || empty($form[$group_name])) { return; } $node = $form['#node']; - $fields = $group['fields']; - $content_fields = content_fields(); - $group_name = $group['group_name']; - - // Use the first field in the group to get the item counts. - $first_field_name = array_shift(array_keys($group['fields'])); - $first_field = isset($content_fields[$first_field_name]) ? $content_fields[$first_field_name] : array(); - $first_field_items = isset($node->$first_field_name) ? $node->$first_field_name : array(); + $content_type = content_types($group['type_name']); + $group_fields = array_intersect_key($content_type['fields'], $group['fields']); + $group_multiple = $group['settings']['multigroup']['multiple']; - $group['multiple'] = $group['settings']['multigroup']['multiple']; - switch ($group['multiple']) { + switch ($group_multiple) { case 0: - $max = 0; + $group_deltas = array(0); + $max_delta = 0; break; + case 1: - // Is this a new node? - if (empty($first_field_items)) { - $max = 1; + // Compute deltas based on the field with the highest number of items. + $group_deltas = array(); + $max_delta = -1; + foreach ($group_fields as $field_name => $field) { + $field_items = isset($node->$field_name) ? $node->$field_name : array(); + if (!empty($field_items)) { + $field = $group_fields[$field_name]; + $field_deltas = array_keys(content_set_empty($field, $field_items)); + $field_max = (!empty($field_deltas) ? max($field_deltas) : 0); + if ($field_max > $max_delta || empty($group_deltas)) { + $max_delta = $field_max; + $group_deltas = $field_deltas; + } + } + } + $current_item_count = (isset($form_state['item_count'][$group_name]) ? $form_state['item_count'][$group_name] : count($group_deltas)); + if ($current_item_count > 0) { + // We always want at least one empty item for the user to fill in. + $current_item_count++; } else { - $filled_items = content_set_empty($first_field, $first_field_items); - $current_item_count = isset($form_state['item_count'][$group_name]) - ? $form_state['item_count'][$group_name] - : count($first_field_items); - // We always want at least one empty icon for the user to fill in. - $max = ($current_item_count > count($filled_items)) - ? $current_item_count - 1 - : count($filled_items); + // Default number of empty items when none is present. + $current_item_count = 1; + } + while (count($group_deltas) < $current_item_count) { + $max_delta++; + $group_deltas[] = $max_delta; } break; + default: - $max = $group['multiple'] - 1; + $group_deltas = array_keys(array_fill(0, $group_multiple, 0)); + $max_delta = $group_multiple - 1; break; } $form[$group_name]['#theme'] = 'content_multigroup_node_form'; - $form[$group_name]['#multiple'] = !empty($max); + $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]['#element_validate'] = array('content_multigroup_node_form_validate'); $form[$group_name]['#tree'] = TRUE; + if (!isset($form['#multigroups'])) { + $form['#multigroups'] = array(); + } + $form['#multigroups'][$group_name] = $group_fields; + + // 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'); + } - for ($delta = 0; $delta <= $max; $delta++) { + 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 ($fields as $field_name => $field) { + 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)) { + if (($add_more = content_multigroup_add_more($form, $form_state, $group)) !== FALSE) { $form[$group_name] += $add_more; } } @@ -617,13 +682,14 @@ function content_multigroup_group_form(& module_load_include('inc', 'content', 'includes/content.node_form'); $node = $form['#node']; - $fields = $group['fields']; - $content_fields = content_fields(); + $type_name = $group['type_name']; + $content_type = content_types($type_name); + $group_fields = array_intersect_key($content_type['fields'], $group['fields']); $group_name = $group['group_name']; - $group['multiple'] = $group['settings']['multigroup']['multiple']; - $form[$group_name]['#fields'] = array_keys($group['fields']); + $group_multiple = $group['settings']['multigroup']['multiple']; + $form[$group_name]['#fields'] = array_keys($group_fields); - foreach ($fields as $field_name => $group_field) { + foreach ($group_fields as $field_name => $field) { if (empty($form[$group_name][$delta])) { $form[$group_name] += array($delta => array($field_name => array())); } @@ -631,19 +697,13 @@ function content_multigroup_group_form(& $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' => $delta, // this 'delta' is the 'weight' element's property + '#delta' => $item_count, // this 'delta' is the 'weight' element's property '#default_value' => $delta, '#weight' => 100, ); - $form[$group_name][$delta]['_delta'] = array( - '#type' => 'hidden', - '#value' => $delta, - ); - - - $field = $content_fields[$field_name]; // Make each field into a pseudo single value field // with the right delta value. @@ -668,325 +728,424 @@ function content_multigroup_group_form(& $node_copy->$field_name = array($delta => NULL); } $form['#node'] = $node_copy; - $field_form = content_field_form($form, $form_state, $field, $delta); - // Place the new $field_form into the $delta position in the group form. + // 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']; - - // Add in our validation step, and make sure it preceeds other - // processing so we can massage the element back to the normal position. - if (empty($form[$group_name][$delta][$field_name]['#element_validate'])) { - $form[$group_name][$delta][$field_name]['#element_validate'] = array(); - } - array_unshift($form[$group_name][$delta][$field_name]['#element_validate'], 'content_multigroup_node_item_validate'); } // 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; } /** - * Swap transposed field/delta values back - * to their normal positions in the node. - */ -function content_multigroup_node_item_validate($element, &$form_state) { - static $weights = array(); + * 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. + 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, FALSE); + } + } - //dsm($form_state['values']); - $form_values = $form_state['values']; - $field_name = array_pop($element['#parents']); - $delta = array_pop($element['#parents']); - $group_name = array_pop($element['#parents']); - - // Identify the new delta value for each field. - - // Find the original delta values for this group, save as static value - // because the group will acquire and lose values while we process it. - if (!array_key_exists($group_name, $weights)) { - $items = $form_state['values'][$group_name]; - $weights[$group_name] = array(); - foreach ($items as $count => $value) { - // Allow for the possibility of matching _weights and missing deltas. - $weight = floatval($value['_weight']); - $old_delta = intval($value['_delta']); - if (empty($weights[$group_name][$weight]) || !in_array($old_delta, $weights[$group_name][$weight])) { - $weights[$group_name][$weight][] = $old_delta; - } - } - ksort($weights[$group_name]); - } - $count = 0; - foreach ($weights[$group_name] as $weight => $values) { - foreach ($values as $old_delta) { - if ($old_delta === $delta) { - $delta = $count; - //dsm('moving delta values: '.$group_name.'>'.$field_name.'>'.'from '. $old_delta .' to '. $delta); - break 2; - } - $count++; - } - } - // We figured out what the new order for the fields is, - // so set the value for the new delta. - - // We move these new values back up to the top level of the - // node and out of the group so the Content module will find and - // save the new values and so they don't get mixed into the - // remaining, unaltered, values in the group. - array_push($element['#parents'], $field_name); - array_push($element['#parents'], $delta); - - // It's very important to use $form_values instead of $element['#value'] - // here, because $element['#value'] is sometimes missing changes - // made in #element_validate processing done by other modules. - $value = isset($form_values[$group_name][$delta][$field_name]) ? $form_values[$group_name][$delta][$field_name] : NULL; - - // Fields that use optionwidgets have an extra array level in the value - // because of the optionwidgets transposition that forces a delta value - // into the result array. This works fine when a delta value is between - // the field name and the field value, as in normal nodes, but not when - // we reverse the field and the delta, so in this case we need to - // promote the nested delta value back up to the field level. - if (is_array($value) && content_multigroup_uses_optionwidgets($field_name, $element['#type_name'])) { - $value = array_shift($value); + 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_fix_values($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); + } } - //dsm('setting value of '. $field_name.'>'.$delta); - //dsm($value); - form_set_value($element, $value, $form_state); + return $form; } /** - * Helper function for identifying fields that use - * optionwidgets transpositions. + * 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_uses_optionwidgets($field_name, $type_name) { - static $optionwidgets; - if (empty($optionwidgets)) { - $optionwidgets = array( - 'optionwidgets_select', - 'optionwidgets_buttons', - 'optionwidgets_onoff', - 'nodereference_buttons', - 'nodereference_select', - 'userreference_buttons', - 'userreference_select', - ); - // Add hook where other widgets that use optionwidgets can announce it. - $optionwidgets = array_merge($optionwidgets, module_invoke_all('content_multigroup_uses_optionwidgets')); - } +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; + } - $types = content_types($type_name); - $fields = $types['fields']; - $field = $fields[$field_name]; - if (in_array($field['widget']['type'], $optionwidgets)) { - return TRUE; + // Recurse through all children elements. + content_multigroup_node_form_fix_required($elements[$key], $required_fields, $required); + } } - return FALSE; } /** - * Validation for the whole node group. + * Node form validation handler. + * + * If all fields in a delta subgroup are empty, then we can let the + * content module remove them. + * + * @see content_set_empty(). */ -function content_multigroup_node_form_validate($element, $form_state) { - // We moved all the new field values out of the field group - // and up to the top level of the node, now get rid of the - // original group values. - form_set_value($element, NULL, $form_state); - return; +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; + + $empty_required_fields = array(); + foreach ($group_fields as $field_name => $field) { + if ($field['required']) { + $empty_required_fields[$field_name] = array(); + } + } + + $empty_deltas = array(); + foreach ($group_fields as $field_name => $field) { + $is_empty_function = $field['module'] .'_content_is_empty'; + foreach ($form_state['values'][$field_name] as $delta => $item) { + if (!isset($empty_deltas[$delta])) { + $empty_deltas[$delta] = TRUE; + } + if (!($is_empty = $is_empty_function($item, $field))) { + $empty_deltas[$delta] = FALSE; + } + $form_state['values'][$field_name][$delta]['_is_empty'] = $is_empty; + } + } + + if ($group_required) { + $non_empty_count = count(array_filter($empty_deltas, create_function('$a', 'return !$a;'))); + if ($non_empty_count == 0) { + form_set_error('', t('Group %name requires one collection of fields minimum.', array('%name' => $group_name))); + } + } + + foreach ($empty_deltas as $delta => $is_empty) { + foreach ($group_fields as $field_name => $field) { + $form_state['values'][$field_name][$delta]['_keep_empty'] = $is_empty ? 0 : 1; + + if (isset($empty_required_fields[$field_name]) && !$is_empty && $form_state['values'][$field_name][$delta]['_is_empty']) { + $empty_required_fields[$field_name][$delta] = $form_state['values'][$field_name][$delta]; + } + unset($form_state['values'][$field_name][$delta]['_is_empty']); + } + } + + foreach ($empty_required_fields as $field_name => $items) { + foreach ($items as $delta => $item) { + if (!empty($item['_error_element'])) { + $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 = ''; + } + form_set_error($error_element, t('%name field is required.', array('%name' => $form[$group_name][$delta][$field_name]['#title']))); + } + } + } } /** - * Implementation of hook_fieldgroup_view(). + * Fix value positions in $form_state for the fields in a multigroup. */ -function content_multigroup_fieldgroup_view(&$node, &$element, $group, $context) { - if ($group['group_type'] != 'multigroup') { - return; +function content_multigroup_node_form_fix_values(&$form, &$form_state, $type_name, $group_name) { + $content_type = content_types($type_name); + $groups = fieldgroup_groups($type_name); + $group = $groups[$group_name]; + $group_fields = array_intersect_key($content_type['fields'], $group['fields']); + + // 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 the field weight from the group and keep track of the current + // delta for each field item. + $group_data[$field_name][$delta] = array_merge($items[$field_name], array( + '_weight' => $items['_weight'], + '_old_delta' => $delta, + )); + } } - $group_name = $group['group_name']; - $node_copy = drupal_clone($node); - $max = $group['settings']['multigroup']['multiple']; + $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; + } - $count = 0; - foreach ($group['fields'] as $field_name => $field) { - $count = max($count, count($node->$field_name)); - } + // Get rid of the old delta value. + foreach (array_keys($items) as $delta) { + unset($items[$delta]['_old_delta']); + } - $group['multiple'] = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1; - $labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array(); - $format = isset($group['settings']['multigroup']['display_settings'][$context]['format']) ? $group['settings']['multigroup']['display_settings'][$context]['format'] : 'fieldset'; - $show_label = isset($group['settings']['multigroup']['display_settings']['label']) ? $group['settings']['multigroup']['display_settings']['label'] : 'above'; + // 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]); + } + } - switch ($group['multiple']) { - case 0: - $max = 0; - break; - case 1: - $max = $count; - break; - default: - $max = $group['multiple']; - break; + // Move field items back to their original positions. + $form_state['values'][$field_name] = $items; } - for ($delta = 0; $delta < $max; $delta++) { - $element[$delta] = array('#weight' => $delta); + // Finally, get rid of the group data in form values. + unset($form_state['values'][$group_name]); +} - $label = !empty($labels[$delta]) && $show_label == 'above' ? $labels[$delta] : ''; +/** + * 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]) { - foreach ($group['fields'] as $field_name => $field) { + // Fix the second item, the delta value, of the element's '#parents' array. + $elements[$key]['#parents'][1] = $form_deltas[$elements[$key]['#parents'][1]]; - // Create a pseudo node that only has the value we want - // in this group and pass it to the formatter. - if (isset($node->content[$field_name])) { - $node_copy->content[$field_name]['field']['items'] = array( - $delta => isset($node->content[$field_name]['field']['items'][$delta]) ? $node->content[$field_name]['field']['items'][$delta] : NULL, - ); - $element[$delta][$field_name] = $node_copy->content[$field_name]; - $element[$delta][$field_name]['#delta'] = $delta; + // 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']]; } - } - switch ($format) { - case 'table': - $element[$delta]['#theme'] = 'content_multigroup_display_table'; - $element[$delta]['#title'] = $label; - break; - case 'fieldset': - $element[$delta]['#type'] = 'fieldset'; - $element[$delta]['#title'] = $label; - break; - case 'hr': - $element[$delta]['#theme'] = 'content_multigroup_display_hr'; - $element[$delta]['#title'] = $label; - break; - default: - $element[$delta]['#theme'] = 'content_multigroup_display_simple'; - $element[$delta]['#title'] = $label; - break; - } - } - - foreach ($group['fields'] as $field_name => $field) { - if (isset($element[$field_name])) { - unset($element[$field_name]); + // Recurse through all children elements. + content_multigroup_node_form_fix_deltas($elements[$key], $form_deltas); } } } /** - * Theme an individual form element. + * Fix form element parents for all fields in multigroups. * - * Combine multiple values into a table with drag-n-drop reordering. - */ -function theme_content_multigroup_node_form($element) { - $output = ''; - if ($element['#multiple'] >= 1) { - $table_id = $element['#group_name'] .'_values'; - $order_class = $element['#group_name'] .'-delta-order'; - - $header = array( - array( - 'data' => '', - 'colspan' => 2 - ), - t('Order'), - ); - $rows = array(); - $groups = fieldgroup_groups($element['#type_name']); - $group = $groups[$element['#group_name']]; - $labels = isset($group['settings']['multigroup']['labels']) ? $group['settings']['multigroup']['labels'] : array(); - $multiple = isset($group['settings']['multigroup']['multiple']) ? $group['settings']['multigroup']['multiple'] : 1; + * 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']); - $i = 0; - foreach (element_children($element) as $delta => $key) { - if ($key !== $element['#group_name'] .'_add_more') { - $label = !empty($labels[$i]) ? theme('content_multigroup_node_label', $labels[$i]) : ''; - $element[$key]['_weight']['#attributes']['class'] = $order_class; - $delta_element = drupal_render($element[$key]['_weight']); - $cells = array( - array('data' => '', 'class' => 'content-multiple-drag'), - $label . drupal_render($element[$key]), - array('data' => $delta_element, 'class' => 'delta-order'), - ); - $rows[] = array( - 'data' => $cells, - // TODO Tablesort drag n drop is not working with complex - // field validation. The fields appear to work correctly, - // but element validation seems to get missed or confused - // causing validation errors. Need to investigate why. - 'class' => 'draggable', - ); + // Now, insert field name and delta to the #parents array. + array_unshift($elements[$key]['#parents'], $field_name, $delta); } - $i++; + + // Recurse through all children elements. + content_multigroup_node_form_fix_parents($elements[$key], $multigroups); } + } +} - $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'content-multiple-table')); - $output .= $element['#description'] ? '
'. $element['#description'] .'
' : ''; - $output .= drupal_render($element[$element['#group_name'] .'_add_more']); +/** + * Update posting data to reflect delta changes in the form structure. + * + * The $_POST array is fixed in content_multigroup_node_form_fix_values(). + */ +function content_multigroup_node_form_fix_post(&$elements) { + foreach (element_children($elements) as $key) { + if (isset($elements[$key]) && $elements[$key]) { - drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class); - } - else { - foreach (element_children($element) as $key) { - $output .= drupal_render($element[$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]); } } - return $output; + // Update the form copy of the $_POST array. + $elements['#post'] = $_POST; } +/** + * 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']; + $value = $form_state['values'][$field_name][$delta][0]; + 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) { - // Add AHAH add more button, if not working with a programmed form. - $multiple = $group['settings']['multigroup']['multiple']; - $form_element = array(); - if ($multiple != 1 || !empty($form['#programmed'])) { - return $form_element; + $group_multiple = $group['settings']['multigroup']['multiple']; + if ($group_multiple != 1 || !empty($form['#programmed'])) { + return FALSE; } - else { - // Make sure the form is cached so ahah can work. - $form['#cache'] = TRUE; - $content_type = content_types($group['type_name']); - $group_name = $group['group_name']; - $group_name_css = str_replace('_', '-', $group_name); - - $form_element[$group_name .'_add_more'] = array( - '#type' => 'submit', - '#name' => $group_name .'_add_more', - '#value' => t('Add more values'), - '#weight' => $multiple + 1, - // Submit callback for disabled JavaScript. drupal_get_form() might get - // the form from the cache, so we can't rely on content_form_alter() - // including this file. Therefore, call a proxy function to do this. - '#submit' => array('content_multigroup_add_more_submit_proxy'), - '#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_add_more_submit handler will find - // the relevant field using these entries. - '#group_name' => $group_name, - '#type_name' => $group['type_name'], - ); - // Add wrappers for the group and 'more' button. - // TODO: could be simplified ? - $form_element['#prefix'] = '
'; - $form_element[$group_name .'_add_more']['#prefix'] = '
'; - $form_element[$group_name .'_add_more']['#suffix'] = '
'; - } + // 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 field using these entries. + '#group_name' => $group_name, + '#type_name' => $group['type_name'], + ); + + // Add wrappers for the group and 'more' button. + // TODO: could be simplified ? + $form_element['#prefix'] = '
'; + $form_element[$group_name .'_add_more']['#prefix'] = '
'; + $form_element[$group_name .'_add_more']['#suffix'] = '
'; + return $form_element; } @@ -1003,7 +1162,7 @@ function content_multigroup_add_more_sub // Make the changes we want to the form state. if ($form_state['values'][$group_name][$group_name .'_add_more']) { - $form_state['item_count'][$group_name] = count($form_state['values'][$group_name]); + $form_state['item_count'][$group_name] = count($form_state['values'][$group_name]) - 1; } } @@ -1013,12 +1172,11 @@ function content_multigroup_add_more_sub * Adapted from content_add_more_js to work with groups instead of fields. */ function content_multigroup_add_more_js($type_name_url, $group_name) { - $type = content_types($type_name_url); - $groups = fieldgroup_groups($type['type']); + $content_type = content_types($type_name_url); + $groups = fieldgroup_groups($content_type['type']); $group = $groups[$group_name]; - $group['multiple'] = $group['settings']['multigroup']['multiple']; - if (($group['multiple'] != 1) || empty($_POST['form_build_id'])) { + if (($group['settings']['multigroup']['multiple'] != 1) || empty($_POST['form_build_id'])) { // Invalid request. drupal_json(array('data' => '')); exit; @@ -1059,17 +1217,23 @@ function content_multigroup_add_more_js( unset($form_state['values'][$group_name][$group['group_name'] .'_add_more']); foreach ($_POST[$group_name] as $delta => $item) { $form_state['values'][$group_name][$delta]['_weight'] = $item['_weight']; - $form_state['values'][$group_name][$delta]['_delta'] = $item['_delta']; } + $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. - - $form_state['item_count'] = array($group_name => count($_POST[$group_name]) + 1); $delta = max(array_keys($_POST[$group_name])) + 1; + $form_state['item_count'] = array($group_name => count($_POST[$group_name])); 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); @@ -1086,7 +1250,6 @@ function content_multigroup_add_more_js( // 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'] : '') .'
'; @@ -1105,46 +1268,245 @@ function content_multigroup_add_more_js( } /** + * Implementation of hook_fieldgroup_view(). + */ +function content_multigroup_fieldgroup_view(&$node, &$element, $group, $context) { + if ($group['group_type'] != 'multigroup') { + return; + } + + $group_name = $group['group_name']; + $node_copy = drupal_clone($node); + $content_type = content_types($group['type_name']); + $group_fields = array_intersect_key($content_type['fields'], $group['fields']); + $group_multiple = $group['settings']['multigroup']['multiple']; + $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(); + $subgroup_format = isset($subgroup_settings[$context]['format']) ? $subgroup_settings[$context]['format'] : 'fieldset'; + + + switch ($group_multiple) { + case 0: + $group_deltas = array(0); + break; + + case 1: + // Compute deltas based on the field with the highest number of items. + $group_deltas = array(); + $max_delta = -1; + foreach (array_keys($group_fields) as $field_name) { + $field_deltas = is_array($node->content[$field_name]['field']['items']) ? array_keys($node->content[$field_name]['field']['items']) : array(); + $field_max = (!empty($field_deltas) ? max($field_deltas) : 0); + if ($field_max > $max_delta) { + $max_delta = $field_max; + $group_deltas = $field_deltas; + } + } + break; + + default: + $group_deltas = array_keys(array_fill(0, $group_multiple - 1, 0)); + break; + } + + foreach ($group_deltas as $i => $delta) { + $element[$delta] = array( + '#title' => ($show_label == 'above' && !empty($subgroup_labels[$i]) ? check_plain(t($subgroup_labels[$i])) : ''), + '#attributes' => array('class' => 'content-multigroup-wrapper content-multigroup-'. $i), + '#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[$delta]['#theme'] = 'content_multigroup_display_simple'; + break; + case 'fieldset': + $element[$delta]['#type'] = 'content_multigroup_display_fieldset'; + break; + case 'hr': + $element[$delta]['#theme'] = 'content_multigroup_display_hr'; + break; + case 'table': + $element['#theme'] = 'content_multigroup_display_table'; + $element['#attributes']['class'] = 'content-multigroup-display-table'; + $element['#fields'] = $group_fields; + break; + } + } + + foreach (array_keys($group_fields) as $field_name) { + if (isset($element[$field_name])) { + unset($element[$field_name]); + } + } +} + +/** + * Theme an individual form element. + * + * Combine multiple values into a table with drag-n-drop reordering. + */ +function theme_content_multigroup_node_form($element) { + $groups = fieldgroup_groups($element['#type_name']); + $group_name = $element['#group_name']; + $group = $groups[$group_name]; + $group_multiple = $group['settings']['multigroup']['multiple']; + $output = ''; + + if ($group_multiple >= 1) { + $table_id = $element['#group_name'] .'_values'; + $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; + + $header = array(array('data' => '')); + if ($multiple_columns) { + $content_type = content_types($group['type_name']); + $group_fields = array_intersect_key($content_type['fields'], $group['fields']); + foreach ($group_fields as $field_name => $field) { + $header[] = check_plain(t($field['widget']['label'])); + } + $table_class = 'content-multigroup-edit-table-multiple-columns'; + } + else { + $header[0]['colspan'] = 2; + $table_class = 'content-multigroup-edit-table-one-column'; + } + $header[] = t('Order'); + $rows = array(); + + $i = 0; + foreach (element_children($element) as $delta => $key) { + if ($key !== $group_name .'_add_more') { + $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; + $delta_element = drupal_render($element[$key]['_weight']); + $cells = array(array('data' => '', 'class' => 'content-multiple-drag')); + if ($multiple_columns) { + foreach ($group_fields as $field_name => $field) { + $cells[] = drupal_render($element[$key][$field_name]); + } + } + else { + $cells[] = $label . drupal_render($element[$key]); + } + $cells[] = array('data' => $delta_element, 'class' => 'delta-order'); + $rows[] = array('data' => $cells, 'class' => 'draggable'); + } + $i++; + } + + $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => $table_class)); + $output .= $element['#description'] ? '
'. $element['#description'] .'
' : ''; + $output .= drupal_render($element[$group_name .'_add_more']); + + drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class); + } + else { + foreach (element_children($element) as $key) { + $output .= drupal_render($element[$key]); + } + } + + return $output; +} + +/** * Theme the sub group label in the node form. */ function theme_content_multigroup_node_label($text) { - if (!empty($text)) { - return '

'. check_plain($text) .'

'; - } + return !empty($text) ? '

'. $text .'

' : ''; } +/** + * Theme a subgroup of fields in 'simple' format. + * + * No output is generated if all fields are empty. + */ function theme_content_multigroup_display_simple($element) { - $label = ''; - if (!empty($element['#title'])) { - $label .= ''; - } - $output = $label; + $children = $output = ''; foreach (element_children($element) as $key) { - $output .= drupal_render($element[$key]); + $children .= drupal_render($element[$key]); + } + if (!empty($children)) { + $output .= ''; + if (!empty($element['#title'])) { + $output .= ''; + } + $output .= $children .''; } return $output; } -function theme_content_multigroup_display_hr($element) { - $label = ''; - if (!empty($element['#title'])) { - $label .= ''; +/** + * 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 ''; } - $output = '
'. $label; + 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) { - $output .= drupal_render($element[$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 'table' format. + * + * No output is generated if all fields are empty. + */ function theme_content_multigroup_display_table($element) { - $label = ''; - if (!empty($element['#title'])) { - $label .= ''; - } - $output = $label; - foreach (element_children($element) as $key) { - $output .= drupal_render($element[$key]); + $header = array(); + foreach ($element['#fields'] as $field_name => $field) { + $header[] = check_plain(t($field['widget']['label'])); + } + $rows = array(); + foreach (element_children($element) as $delta) { + $cells = array(); + foreach ($element['#fields'] as $field_name => $field) { + $cells[] = array('data' => drupal_render($element[$delta][$field_name]), 'class' => $element[$delta]['#attributes']['class']); + } + $rows[] = $cells; } - return $output; -} \ No newline at end of file + return count($rows) ? theme('table', $header, $rows, $element['#attributes']) : ''; +}