? .cache ? .settings Index: modules/field/field.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.api.php,v retrieving revision 1.50 diff -u -p -r1.50 field.api.php --- modules/field/field.api.php 12 Nov 2009 21:03:36 -0000 1.50 +++ modules/field/field.api.php 24 Nov 2009 17:34:26 -0000 @@ -621,15 +621,16 @@ function hook_field_widget_info_alter(&$ * contains the base form element properties derived from the field * configuration. * - * Field API will set the weight, field name and delta values for each - * form element. If there are multiple values for this field, the - * Field API will call this function as many times as needed. + * @todo note about $form_state['storage']['field_info'] + * + * Field API will set the weight, field name and delta values for each form + * element. If there are multiple values for this field, the Field API will + * call this function as many times as needed. * * @param $form * The entire form array. * @param $form_state - * The form_state, $form_state['values'][$field['field_name']] - * holds the field's form values. + * An associative array containing the current state of the form. * @param $field * The field structure. * @param $instance @@ -645,6 +646,7 @@ function hook_field_widget_info_alter(&$ * - #object_type: The name of the object the field is attached to. * - #bundle: The name of the field bundle the field is contained in. * - #field_name: The name of the field. + * - #language: The language the field is being edited in. * - #columns: A list of field storage columns of the field. * - #title: The sanitized element label for the field instance, ready for * output. @@ -655,7 +657,6 @@ function hook_field_widget_info_alter(&$ * required. * - #delta: The order of this item in the array of subelements; see $delta * above. - * * @return * The form elements for a single widget for this field. */ @@ -680,8 +681,12 @@ function hook_field_widget(&$form, &$for * - 'error': the error code. Complex widgets might need to report different * errors to different form elements inside the widget. * - 'message': the human readable message to be displayed. + * @param $form + * The form array. + * @param $form_state + * An associative array containing the current state of the form. */ -function hook_field_widget_error($element, $error) { +function hook_field_widget_error($element, $error, $form, &$form_state) { form_error($element['value'], $error['message']); } Index: modules/field/field.attach.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v retrieving revision 1.59 diff -u -p -r1.59 field.attach.inc --- modules/field/field.attach.inc 8 Nov 2009 19:11:56 -0000 1.59 +++ modules/field/field.attach.inc 24 Nov 2009 14:24:35 -0000 @@ -414,21 +414,10 @@ function _field_invoke_multiple_default( * is provided the default site language will be used. * @return * The form elements are added by reference at the top level of the $form - * parameter. Sample structure: + * parameter. Processing information is added by reference in + * $form_state['storage']['field_info']. + * Sample structure for $form: * @code - * array( - * '#fields' => array( - * // One sub-array per field appearing in the form, keyed by field name. - * 'field_foo' => array ( - * 'field' => the field definition structure, - * 'instance' => the field instance definition structure, - * 'form_path' => an array of keys indicating the path to the field - * element within the full $form structure, used by the 'add more - * values' AHAH button. Any 3rd party module using form_alter() to - * modify the structure of the form should update this entry as well. - * ), - * ), - * * // One sub-array per field appearing in the form, keyed by field name. * // The structure of the array differs slightly depending on whether the * // widget is 'single-value' (provides the input for one field value, @@ -436,12 +425,12 @@ function _field_invoke_multiple_default( * // needed, or 'multiple-values' (one single widget allows the input of * // several values, e.g checkboxes, select box...). * // The sub-array is nested into a $langcode key where $langcode has the - * // same value of the $langcode parameter above. This allow us to match - * // the field data structure ($field_name[$langcode][$delta][$column]). + * // same value of the $langcode parameter above. * // The '#language' key holds the same value of $langcode and it is used * // to access the field sub-array when $langcode is unknown. * 'field_foo' => array( * '#tree' => TRUE, + * '#field_name' => the name of the field * '#language' => $langcode, * $langcode => array( * '#field_name' => the name of the field, @@ -481,6 +470,26 @@ function _field_invoke_multiple_default( * ), * ) * @endcode + * + * Sample structure for $form_state['storage']['field_info']: @todo + * @code + * array( + * // One sub-array per field appearing in the form, keyed by field name. + * 'field_foo' => array ( + * // One sub-array per language the field is being edited in. + * // @todo compare with above. + * 'xxz' => array ( + * 'field' => the field definition structure, + * 'instance' => the field instance definition structure, + * 'parents' => @todo + * 'array_parents' => @todo an array of keys indicating the path to the field + * element within the full $form structure, used by the 'add more + * values' AHAH button. + * 'errors' => @todo + * ), + * ), + * ), + * */ function field_attach_form($obj_type, $object, &$form, &$form_state, $langcode = NULL) { // If no language is provided use the default site language. @@ -743,7 +752,12 @@ function field_attach_form_validate($obj catch (FieldValidationException $e) { // Pass field-level validation errors back to widgets for accurate error // flagging. - _field_invoke_default('form_errors', $obj_type, $object, $form, $e->errors); + foreach ($e->errors as $field_name => $field_errors) { + foreach ($field_errors as $langcode => $language_errors) { + $form_state['storage']['field_info'][$field_name][$langcode]['errors'] = $language_errors; + } + } + _field_invoke_default('form_errors', $obj_type, $object, $form, $form_state); } } Index: modules/field/field.default.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.default.inc,v retrieving revision 1.23 diff -u -p -r1.23 field.default.inc --- modules/field/field.default.inc 9 Nov 2009 18:23:41 -0000 1.23 +++ modules/field/field.default.inc 24 Nov 2009 14:24:35 -0000 @@ -11,13 +11,31 @@ * the corresponding field_attach_[operation]() function. */ +/** + * @todo + * @param $obj_type + * @param $object + * @param $field + * @param $instance + * @param $langcode + * @param $items + * @param $form + * @param $form_state + */ function field_default_extract_form_values($obj_type, $object, $field, $instance, $langcode, &$items, $form, &$form_state) { $field_name = $field['field_name']; + $field_info = $form_state['storage']['field_info'][$field_name][$langcode]; + + // Navigate to the right part of the submitted values. + $values = $form_state['values']; + foreach ($field_info['parents'] as $key) { + $values = isset($values[$key]) ? $values[$key] : NULL; + } - if (isset($form_state['values'][$field_name][$langcode])) { - $items = $form_state['values'][$field_name][$langcode]; + if (isset($values)) { // Remove the 'value' of the 'add more' button. - unset($items['add_more']); + unset($values['add_more']); + $items = $values; } } Index: modules/field/field.form.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.form.inc,v retrieving revision 1.35 diff -u -p -r1.35 field.form.inc --- modules/field/field.form.inc 20 Nov 2009 04:51:27 -0000 1.35 +++ modules/field/field.form.inc 24 Nov 2009 17:38:21 -0000 @@ -17,16 +17,15 @@ function field_default_form($obj_type, $ } $field_name = $field['field_name']; + $addition[$field_name] = array(); - // Put field information at the top of the form, so that it can be easily - // retrieved. - // Note : widgets and other form handling code should *always* fetch field - // and instance information from $form['#fields'] rather than from - // field_info_field(). This lets us build forms for 'variants' of a field, - // for instance on admin screens. - $form['#fields'][$field_name] = array( + // Store field information in $form_state['storage']. + $form_state['storage']['field_info'][$field_name][$langcode] = array( 'field' => $field, 'instance' => $instance, + 'parents' => array(), + 'array_parents' => array(), + 'errors' => array(), ); // Populate widgets with default values when creating a new object. @@ -34,13 +33,13 @@ function field_default_form($obj_type, $ $items = field_get_default_value($obj_type, $object, $field, $instance, $langcode); } - $field_elements = array(); - + // Collect widget elements. + $elements = array(); if (field_access('edit', $field, $obj_type, $object)) { // If field module handles multiple values for this form element, and we // are displaying an individual element, process the multiple value form. if (!isset($get_delta) && field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { - $field_elements = field_multiple_value_form($field, $instance, $langcode, $items, $form, $form_state); + $elements = field_multiple_value_form($field, $instance, $langcode, $items, $form, $form_state); } // If the widget is handling multiple values (e.g Options), or if we are // displaying an individual element, just get a single form element and @@ -53,6 +52,7 @@ function field_default_form($obj_type, $ '#object_type' => $instance['object_type'], '#bundle' => $instance['bundle'], '#field_name' => $field_name, + '#language' => $langcode, '#columns' => array_keys($field['columns']), '#title' => check_plain(t($instance['label'])), '#description' => field_filter_xss($instance['description']), @@ -67,23 +67,17 @@ function field_default_form($obj_type, $ // assumptions about how the field is structured, just merge in the // returned element. if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) { - $field_elements[$delta] = $element; + $elements[$delta] = $element; } else { - $field_elements = $element; + $elements = $element; } } } } } - if ($field_elements) { - // Add the field form element as a child keyed by language code to match - // the field data structure: - // $object->{$field_name}[$langcode][$delta][$column]. - // The '#language' key can be used to access the field's form element - // when $langcode is unknown. The #weight property is inherited from the - // field's form element. + if ($elements) { // Also aid in theming of field widgets by rendering a classified // container. $addition[$field_name] = array( @@ -95,29 +89,32 @@ function field_default_form($obj_type, $ 'field-widget-' . drupal_html_class($instance['widget']['type']), ), ), - '#tree' => TRUE, '#weight' => $instance['widget']['weight'], - '#language' => $langcode, - $langcode => $field_elements, ); } else { // The field is not accessible, or the widget did not return anything. Make // sure the items are available in the submitted form values. foreach ($items as $delta => $item) { - $field_elements[$delta] = array( + $elements[$delta] = array( '#type' => 'value', '#value' => $item, ); } - $addition[$field_name] = array( - '#tree' => TRUE, - '#language' => $langcode, - $langcode => $field_elements, - ); } - $form['#fields'][$field_name]['form_path'] = array($field_name); + // @todo A word of explanation. + $elements['#after_build'][] = 'field_form_element_after_build'; + $elements['#field_name'] = $field_name; + $elements['#language'] = $langcode; + + $addition[$field_name] += array( + '#tree' => TRUE, + // The '#language' key can be used to access the field's form element + // when $langcode is unknown. + '#language' => $langcode, + $langcode => $elements, + ); return $addition; } @@ -153,7 +150,7 @@ function field_multiple_value_form($fiel $title = check_plain(t($instance['label'])); $description = field_filter_xss(t($instance['description'])); - $wrapper_id = drupal_html_class($field_name) . '-wrapper'; + $wrapper_id = drupal_html_class($field_name) . '-add-more-wrapper'; $field_elements = array(); $function = $instance['widget']['module'] . '_field_widget'; @@ -164,6 +161,7 @@ function field_multiple_value_form($fiel '#object_type' => $instance['object_type'], '#bundle' => $instance['bundle'], '#field_name' => $field_name, + '#language' => $langcode, '#columns' => array_keys($field['columns']), // For multiple fields, title and description are handled by the wrapping table. '#title' => $multiple ? '' : $title, @@ -299,36 +297,61 @@ function theme_field_multiple_value_form return $output; } +/** + * Stores information about the built form structure in the form storage. + * + * This ensures Field API form features do not break if the form structure is + * modified in hook_form_alter(). + * + * The 'parents' array is used by to extract field values from the submitted + * values. + * @see field_default_extract_form_values() + * + * The 'array_parents' array is used to assign validation errors to actual form + * elements, and to identify the form element to return in the AJAX 'add more' + * button handler. + * @see field_default_form_errors() + * @see field_add_more_js() + */ +function field_form_element_after_build($element, &$form_state) { + $field_name = $element['#field_name']; + $langcode = $element['#language']; + $form_state['storage']['field_info'][$field_name][$langcode]['parents'] = $element['#parents']; + $form_state['storage']['field_info'][$field_name][$langcode]['array_parents'] = $element['#array_parents']; + + return $element; +} /** * Transfer field-level validation errors to widgets. */ -function field_default_form_errors($obj_type, $object, $field, $instance, $langcode, $items, $form, $errors) { +function field_default_form_errors($obj_type, $object, $field, $instance, $langcode, $items, $form, &$form_state) { $field_name = $field['field_name']; - if (!empty($errors[$field_name][$langcode])) { + $field_info = $form_state['storage']['field_info'][$field_name][$langcode]; + + if (!empty($field_info['errors'])) { $function = $instance['widget']['module'] . '_field_widget_error'; $function_exists = function_exists($function); // Walk the form down to where the widget lives. - $form_path = $form['#fields'][$field_name]['form_path']; $element = $form; - foreach ($form_path as $key) { + foreach ($field_info['array_parents'] as $key) { $element = $element[$key]; } $multiple_widget = field_behaviors_widget('multiple values', $instance) != FIELD_BEHAVIOR_DEFAULT; - foreach ($errors[$field_name][$langcode] as $delta => $delta_errors) { + foreach ($field_info['errors'] as $delta => $delta_errors) { // For multiple single-value widgets, pass errors by delta. // For a multiple-value widget, all errors are passed to the main widget. - $error_element = $multiple_widget ? $element[$langcode] : $element[$langcode][$delta]; + $error_element = $multiple_widget ? $element : $element[$delta]; foreach ($delta_errors as $error) { if ($function_exists) { - $function($error_element, $error); + $function($error_element, $error, $form, $form_state); } else { // Make sure that errors are reported (even incorrectly flagged) if // the widget module fails to implement hook_field_widget_error(). - form_error($error_element, $error['error']); + form_error($error_element, $error['error'], $form, $form_state); } } } @@ -360,22 +383,25 @@ function field_add_more_submit($form, &$ function field_add_more_js($form, $form_state) { // Retrieve field information. $field_name = $form_state['clicked_button']['#field_name']; - $field = $form['#fields'][$field_name]['field']; + $langcode = $form_state['clicked_button']['#language']; + $field_info = $form_state['storage']['field_info'][$field_name][$langcode]; + $field = $field_info['field']; + if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) { ajax_render(array()); } - // Navigate to the right part of the form. - $form_path = $form['#fields'][$field_name]['form_path']; - $field_form = $form; - foreach ($form_path as $key) { - $field_form = $field_form[$key]; + + // Navigate to the right element in the the form. + $element = $form; + + foreach ($field_info['array_parents'] as $key) { + $element = $element[$key]; } // Add a DIV around the new field to receive the AJAX effect. - $langcode = $field_form['#language']; - $delta = $field_form[$langcode]['#max_delta']; - $field_form[$langcode][$delta]['#prefix'] = '