diff -rupN content.module content.module --- content.module 2008-11-10 16:56:35.000000000 +0100 +++ content.module 2008-12-16 20:14:09.000000000 +0100 @@ -492,13 +492,13 @@ function theme_content_multiple_values($ $items = array(); foreach (element_children($element) as $key) { if ($key !== $element['#field_name'] .'_add_more') { - $items[] = &$element[$key]; + $items[$element[$key]['#delta']] = &$element[$key]; } } - usort($items, '_content_sort_items_value_helper'); + uasort($items, '_content_sort_items_value_helper'); // Add the items as table rows. - foreach ($items as $key => $item) { + foreach ($items as $delta => $item) { $item['_weight']['#attributes']['class'] = $order_class; $delta_element = drupal_render($item['_weight']); $cells = array( @@ -710,22 +710,22 @@ function content_field($op, &$node, $fie } // Filter out empty values. - $items = content_set_empty($field, $items); + $items = content_set_empty($field, $items, FALSE); break; case 'view': $addition = array(); - // Previewed nodes bypass the 'presave' op, so we need to some massaging. - if ($node->build_mode == NODE_BUILD_PREVIEW && content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) { + // Previewed nodes bypass the 'presave' op, so we need to do some massaging. + if ($node->build_mode == NODE_BUILD_PREVIEW) { if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) { // Reorder items to account for drag-n-drop reordering. $items = _content_sort_items($field, $items); } // Filter out empty values. - $items = content_set_empty($field, $items); + $items = content_set_empty($field, $items, FALSE); } // NODE_BUILD_NORMAL is 0, and ('whatever' == 0) is TRUE, so we need a ===. @@ -762,10 +762,10 @@ function content_field($op, &$node, $fie ); // Fill-in items. - foreach ($items as $delta => $item) { + foreach (array_keys($items) as $weight => $delta) { $element['items'][$delta] = array( - '#item' => $item, - '#weight' => $delta, + '#item' => $items[$delta], + '#weight' => $weight, ); } @@ -881,26 +881,54 @@ function content_field($op, &$node, $fie * * @param array $field * @param array $items + * @param boolean $keep_empty_items * @return array * returns filtered and adjusted item array */ -function content_set_empty($field, $items) { - // Filter out empty values. +function content_set_empty($field, $items, $keep_empty_items = TRUE) { + // Prepare an empty item. + $empty = array(); + foreach (array_keys($field['columns']) as $column) { + $empty[$column] = NULL; + } + + // Keep track of empty values. $filtered = array(); $function = $field['module'] .'_content_is_empty'; foreach ((array) $items as $delta => $item) { + // Get the item if it isn't empty. if (!$function($item, $field)) { - $filtered[] = $item; + $filtered[$delta] = $item; + } + // When the item is empty, keep it only if requested to. + else if ($keep_empty_items || (isset($item['_keep_empty']) && $item['_keep_empty'])) { + $filtered[$delta] = $empty; } } // Make sure we store the right number of 'empty' values. - $empty = array(); - foreach (array_keys($field['columns']) as $column) { - $empty[$column] = NULL; + if ($field['multiple'] == 1) { + // Remove last element if empty for multi-valued fields. + $last_item = end($filtered); + if ($function($last_item, $field)) { + unset($filtered[key($filtered)]); + } + } + else { + $pad = $field['multiple'] > 1 ? (int)$field['multiple'] : 1; + $count = count($filtered); + if ($count < $pad) { + $delta = $count > 0 ? max(array_keys($filtered)) + 1 : 0; + for ($i = $count; $i < $pad; $i++) { + $filtered[$delta++] = $empty; + } + } + } + + // Rebuild deltas for fields with multiple values. + if ($field['multiple'] >= 1) { + $filtered = array_values($filtered); } - $pad = $field['multiple'] > 1 ? $field['multiple'] : 1; - $filtered = array_pad($filtered, $pad, $empty); return $filtered; } @@ -913,7 +941,7 @@ function _content_sort_items($field, $it if ($field['multiple'] >= 1 && isset($items[0]['_weight'])) { usort($items, '_content_sort_items_helper'); foreach ($items as $delta => $item) { - if (is_array($items[$delta])) { + if (is_array($item) && isset($item['_weight'])) { unset($items[$delta]['_weight']); } } @@ -994,7 +1022,19 @@ function content_storage($op, $node) { if (!isset($additions[$field_name])) { $additions[$field_name] = array(); } - $additions[$field_name][] = $item; + + // When the node is loaded we need to preserve any possible empty + // item to ensure deltas are in sync for fields in multigroup in + // case the node is loaded and saved programatically. + $item['_keep_empty'] = 1; + + // Preserve deltas when loading items from database. + if (isset($row['delta'])) { + $additions[$field_name][$row['delta']] = $item; + } + else { + $additions[$field_name][] = $item; + } } } } @@ -2381,8 +2421,7 @@ function content_extra_field_weight($typ * Do we want to eliminate them from the results? */ function content_max_delta($field_name, $type_name = NULL) { - $fields = content_fields(); - $field = $fields[$field_name]; + $field = content_fields($field_name); // Non-multiple value fields don't use the delta column, // but could exist in multiple databases. If any value @@ -2393,8 +2432,7 @@ function content_max_delta($field_name, if (empty($type_name) || $content_type['type'] == $type_name) { foreach ($content_type['fields'] as $field) { $db_info = content_database_info($field); - $table = $db_info['table']; - if (db_result(db_query("SELECT COUNT(*) FROM {$table}")) >= 1) { + if (db_result(db_query('SELECT COUNT(*) FROM {'. $db_info['table'] .'}')) >= 1) { return 0; } } @@ -2406,14 +2444,13 @@ function content_max_delta($field_name, // in the node table to limit the type. else { $db_info = content_database_info($field); - $table = $db_info['table']; if (!empty($type_name)) { - $delta = db_result(db_query("SELECT MAX(delta) FROM {$table} f LEFT JOIN {node} n ON f.vid = n.vid WHERE n.type = '%s'", $type_name)); + $delta = db_result(db_query('SELECT MAX(delta) FROM {'. $db_info['table'] ."} f LEFT JOIN {node} n ON f.vid = n.vid WHERE n.type = '%s'", $type_name)); } else { - $delta = db_result(db_query("SELECT MAX(delta) FROM {$table}")); + $delta = db_result(db_query('SELECT MAX(delta) FROM {'. $db_info['table'] .'}')); } - if ($delta >= 0) { + if ($delta !== FALSE && $delta >= 0) { return $delta; } } diff -rupN includes/content.node_form.inc includes/content.node_form.inc --- includes/content.node_form.inc 2008-10-04 15:14:21.000000000 +0200 +++ includes/content.node_form.inc 2008-12-11 01:03:39.000000000 +0100 @@ -157,21 +157,31 @@ function content_multiple_value_form(&$f switch ($field['multiple']) { case 0: + $deltas = array(0); $max = 0; break; - case 1: - $filled_items = content_set_empty($field, $items); - $current_item_count = isset($form_state['item_count'][$field_name]) - ? $form_state['item_count'][$field_name] - : count($items); - // We always want at least one empty icon for the user to fill in. - $max = ($current_item_count > count($filled_items)) - ? $current_item_count - 1 - : $current_item_count; + case 1: + $deltas = array_keys(content_set_empty($field, $items)); + $current_item_count = (isset($form_state['item_count'][$field_name]) ? $form_state['item_count'][$field_name] : count($deltas)); + if ($current_item_count > 0) { + // We always want at least one empty item for the user to fill in. + $current_item_count++; + } + else { + // Default number of empty items when none is present. + $current_item_count = 2; + } + $max = (!empty($deltas) ? max($deltas) : -1); + while (count($deltas) < $current_item_count) { + $max++; + $deltas[] = $max; + } break; + default: $max = $field['multiple'] - 1; + $deltas = array_keys(array_fill(0, $field['multiple'], 0)); break; } @@ -186,7 +196,7 @@ function content_multiple_value_form(&$f ); $function = $field['widget']['module'] .'_widget'; - for ($delta = 0; $delta <= $max; $delta++) { + foreach ($deltas as $delta) { if ($element = $function($form, $form_state, $field, $items, $delta)) { $defaults = array( '#title' => ($field['multiple'] >= 1) ? '' : $title, @@ -212,6 +222,18 @@ function content_multiple_value_form(&$f ); } + // Add a hidden element to allow the multigroup module tell us if content + // module needs to keep track of empty fields. Disabled by default. + // See content_set_empty(). + if ($field['multiple'] == 1) { + // We name the element '_keep_empty' to avoid clashing with column names + // defined by field modules. + $element['_keep_empty'] = array( + '#type' => 'hidden', + '#value' => isset($items[$delta]['_keep_empty']) ? $items[$delta]['_keep_empty'] : 0, + ); + } + $form_element[$delta] = array_merge($element, $defaults); } } @@ -266,7 +288,7 @@ function content_add_more_submit($form, // Make the changes we want to the form state. if ($form_state['values'][$field_name][$field_name .'_add_more']) { - $form_state['item_count'][$field_name] = count($form_state['values'][$field_name]); + $form_state['item_count'][$field_name] = count($form_state['values'][$field_name]) - 1; } } @@ -324,7 +346,8 @@ function content_add_more_js($type_name_ $_POST[$field_name] = _content_sort_items($field, $_POST[$field_name]); // Build our new form element for the whole field, asking for one more element. - $form_state['item_count'] = array($field_name => count($_POST[$field_name]) + 1); + $delta = max(array_keys($_POST[$field_name])) + 1; + $form_state['item_count'] = array($field_name => count($_POST[$field_name])); $form_element = content_field_form($form, $form_state, $field); // Let other modules alter it. drupal_alter('form', $form_element, array(), 'content_add_more_js'); @@ -343,7 +366,6 @@ function content_add_more_js($type_name_ // Build the new form against the incoming $_POST values so that we can // render the new element. - $delta = max(array_keys($_POST[$field_name])) + 1; $_POST[$field_name][$delta]['_weight'] = $delta; $form_state = array('submitted' => FALSE); $form += array(