Assuming I understand CONTENT_HANDLE_MODULE correctly, when 'multiple values' is set to CONTENT_HANDLE_MODULE then a given widget can apply its own handling of a given field.

Now, lets say there is a widget that needs tp modify the data that is presented inside of a multiple value field using this method.
When CONTENT_HANDLE_MODULE is set, 'multiple values' no longer works.
Instead only a single field is provided instead of multiple fields.
When set back to CONTENT_HANDLE_CORE, the multiple fields are again shown instead of a single field.

The problem happens around line 100 of content.node_form.inc.
Specifically this segment of code:

<?php
    // If content module handles multiple values for this form element,
    // and not selecting an individual $delta, process the multiple value form.
    if (!isset($get_delta) && content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
      $form_element = content_multiple_value_form($form, $form_state, $field, $items);
    }
    // If the widget is handling multiple values (e.g optionwidgets),
    // or selecting an individual element, just get a single form
    // element and make it the $delta value.
    else {
      $delta = isset($get_delta) ? $get_delta : 0;
      if ($element = $function($form, $form_state, $field, $items, $delta)) {
        $title = check_plain(t($field['widget']['label']));
        $description = content_filter_xss(t($field['widget']['description']));
        $defaults = array(
          '#required' => $get_delta > 0 ? FALSE : $field['required'],
          '#columns'  => array_keys($field['columns']),
          '#title' => $title,
          '#description' => $description,
          '#delta' => $delta,
          '#field_name' => $field['field_name'],
          '#type_name' => $field['type_name'],
        );
        // If we're processing a specific delta value for a field where the
        // content module handles multiples, set the delta in the result.
        // For fields that handle their own processing, we can't make assumptions
        // about how the field is structured, just merge in the returned value.
        if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
          $form_element[$delta] = array_merge($element, $defaults);
        }
        else {
          $form_element = array_merge($element, $defaults);
        }
      }
    }
?>

What happens here is that when CONTENT_HANDLE_MODULE. is specified, the hook_widget() will never knowingly receive the top-most field where $get_delta is set to NULL.
Instead, the topmost field gets assigned to 0 and is mistaken for the field nested inside of the topmost field at 0.

To make this easier to understand, here is a structure of what happens with a field whose multiple value is set to 3:

  TopMost Field = [
   - Field 0 = [ field 0 elements ]
   - Field 1 = [ field 1 elements ]
   - Field 2 = [ field 2 elements ]
   - ... other TopMost Field elements ...
  ]

  what my_hook_widget currently receives:
  - TopMost Field as [ Field 0 ], gets assigend [ Field 0 elements ]
  - Field 0 as [ Field 0 ], gets assigend [ Field 0 elements ]
  - Field 1 as [ Field 1 ], gets assigend [ Field 1 elements ]
  - Field 2 as [ Field 2 ], gets assigend [ Field 2 elements ]

  what my_hook_widget should receive:
  - TopMost Field as [ NULL ], gets assigend [ TopMost Field elements ]
  - Field 0 as [ Field 0 ], gets assigend [ Field 0 elements ]
  - Field 1 as [ Field 1 ], gets assigend [ Field 1 elements ]
  - Field 2 as [ Field 2 ], gets assigend [ Field 2 elements ]

Pay specific attention to this line: <?php if (!isset($get_delta) && content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) { ?>

See how the content_handle only calls the content_multiple_value_form() function only when $get_delta is NULL?

The solution is not to define a $delta whose NULL value is changed to be 0.
This way the hook_widget() function can tell the difference between the nested field at 0 and the topmost field.

The corrected code looks like the following:

<?php
    // If content module handles multiple values for this form element,
    // and not selecting an individual $delta, process the multiple value form.
    if (!isset($get_delta) && content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
      $form_element = content_multiple_value_form($form, $form_state, $field, $items);
    }
    // If the widget is handling multiple values (e.g optionwidgets),
    // or selecting an individual element, just get a single form
    // element and make it the $delta value.
    else {
      if ($element = $function($form, $form_state, $field, $items, $get_delta)) {
        $title = check_plain(t($field['widget']['label']));
        $description = content_filter_xss(t($field['widget']['description']));
        $defaults = array(
          '#required' => $get_delta > 0 ? FALSE : $field['required'],
          '#columns'  => array_keys($field['columns']),
          '#title' => $title,
          '#description' => $description,
          '#field_name' => $field['field_name'],
          '#type_name' => $field['type_name'],
        );
        if (isset($get_delta)){
          $defaults['#delta'] = $get_delta;
        }
        // If we're processing a specific delta value for a field where the
        // content module handles multiples, set the delta in the result.
        // For fields that handle their own processing, we can't make assumptions
        // about how the field is structured, just merge in the returned value.
        if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) { 
          $form_element[$get_delta] = array_merge($element, $defaults);
        }
        else {
          $form_element = array_merge($element, $defaults);
        }
      }
    }
?>

I removed $delta and replaced it with $get_delta which contains the correct values.

Once this is done, modules can then act on the NULL case without causing weird behavior for the 0th case.

Attached is a patch that makes these changes.

For compatibility with the 2.x version, you might want to instead define a new hook (say hook_widget_parent for example), then that hook will be called for the NULL case while all other cases go to the existing hook_widget(). (Thus preventing existing hook_widget() functions from breaking when their delta that is always assumed to be numeric is suddenly NULL!)

Comments

thekevinday’s picture

It seems I have been having trouble uploading files lately.

Here is the second attempt to upload the patch.

Also, I am now wondering if

<?php
        if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
          $form_element[$get_delta] = array_merge($element, $defaults);
        }
?>

should instead be

<?php
        if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE || isset($get_delta) ) {
          $form_element[$get_delta] = array_merge($element, $defaults);
        }
?>
thekevinday’s picture

I now believe that the additional case mentioned in #1 should be performed.
This patch applies includes the change.

thekevinday’s picture

I am reverting #2 because of:

         // If we're processing a specific delta value for a field where the
         // content module handles multiples, set the delta in the result.
         // For fields that handle their own processing, we can't make assumptions
         // about how the field is structured, just merge in the returned value.

Which is mentioned immediately above the code I modified.
It is a good idea to play it safe here and expect the modules to handle the defaults themselves.