Commit 8d1f703f authored by webchick's avatar webchick
Browse files

#641670 by yched and sun: Move ['#field'] meta information into ().

parent 0b0b4029
......@@ -609,15 +609,25 @@ function hook_field_widget_info_alter(&$info) {
* 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.
* 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.
*
* Note that, depending on the context in which the widget is being included
* (regular object edit form, 'default value' input in the field settings form,
* etc...), the passed in values for $field and $instance might be different
* from the official definitions returned by field_info_field() and
* field_info_instance(). If the widget uses Form API callbacks (like
* #element_validate, #value_callback...) that need to access the $field or
* $instance definitions, they should not use the field_info_*() functions, but
* fetch the information present in $form_state['field']:
* - $form_state['field'][$field_name][$langcode]['field']
* - $form_state['field'][$field_name][$langcode]['instance']
*
* @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
......@@ -633,6 +643,7 @@ function hook_field_widget_info_alter(&$info) {
* - #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.
......@@ -643,7 +654,6 @@ function hook_field_widget_info_alter(&$info) {
* 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.
*/
......@@ -668,8 +678,12 @@ function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langco
* - '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']);
}
......
......@@ -400,6 +400,79 @@ function _field_invoke_multiple_default($op, $obj_type, $objects, &$a = NULL, &$
/**
* Add form elements for all fields for an object to a form structure.
*
* Sample structure for $form:
* @code
* // 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,
* // most common case), and will therefore be repeated as many times as
* // 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.
* // 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,
* '#tree' => TRUE,
* '#required' => whether or not the field is required,
* '#title' => the label of the field instance,
* '#description' => the description text for the field instance,
*
* // Only for 'single' widgets:
* '#theme' => 'field_multiple_value_form',
* '#cardinality' => the field cardinality,
* // One sub-array per copy of the widget, keyed by delta.
* 0 => array(
* '#title' => the title to be displayed by the widget,
* '#default_value' => the field value for delta 0,
* '#required' => whether the widget should be marked required,
* '#delta' => 0,
* '#field_name' => the name of the field,
* '#bundle' => the name of the bundle,
* '#columns' => the array of field columns,
* // The remaining elements in the sub-array depend on the widget.
* '#type' => the type of the widget,
* ...
* ),
* 1 => array(
* ...
* ),
*
* // Only for multiple widgets:
* '#bundle' => $instance['bundle'],
* '#columns' => array_keys($field['columns']),
* // The remaining elements in the sub-array depend on the widget.
* '#type' => the type of the widget,
* ...
* ),
* ...
* ),
* )
* @endcode
*
* Sample structure for $form_state['field']:
* @code
* array(
* // One sub-array per field appearing in the form, keyed by field name.
* 'field_foo' => array(
* $langcode => array(
* 'field' => the field definition array,
* 'instance' => the field instance definition array,
* 'array_parents' => an array of keys indicating the path to the field
* element within the full $form structure. This entry is populated at
* form build time.
* 'errors' => the array ok field validation errors reported on the
* field. This entry is populated at form validation time.
* ),
* ),
* ),
* @endcode
*
* @param $obj_type
* The type of $object; e.g. 'node' or 'user'.
* @param $object
......@@ -414,73 +487,8 @@ function _field_invoke_multiple_default($op, $obj_type, $objects, &$a = NULL, &$
* 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:
* @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,
* // most common case), and will therefore be repeated as many times as
* // 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]).
* // 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,
* '#language' => $langcode,
* $langcode => array(
* '#field_name' => the name of the field,
* '#tree' => TRUE,
* '#required' => whether or not the field is required,
* '#title' => the label of the field instance,
* '#description' => the description text for the field instance,
*
* // Only for 'single' widgets:
* '#theme' => 'field_multiple_value_form',
* '#cardinality' => the field cardinality,
* // One sub-array per copy of the widget, keyed by delta.
* 0 => array(
* '#title' => the title to be displayed by the widget,
* '#default_value' => the field value for delta 0,
* '#required' => whether the widget should be marked required,
* '#delta' => 0,
* '#field_name' => the name of the field,
* '#bundle' => the name of the bundle,
* '#columns' => the array of field columns,
* // The remaining elements in the sub-array depend on the widget.
* '#type' => the type of the widget,
* ...
* ),
* 1 => array(
* ...
* ),
*
* // Only for multiple widgets:
* '#bundle' => $instance['bundle'],
* '#columns' => array_keys($field['columns']),
* // The remaining elements in the sub-array depend on the widget.
* '#type' => the type of the widget,
* ...
* ),
* ...
* ),
* )
* @endcode
* parameter. Processing information is added by reference in
* $form_state['field'].
*/
function field_attach_form($obj_type, $object, &$form, &$form_state, $langcode = NULL) {
// If no language is provided use the default site language.
......@@ -748,7 +756,12 @@ function field_attach_form_validate($obj_type, $object, $form, &$form_state) {
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['field'][$field_name][$langcode]['errors'] = $language_errors;
}
}
_field_invoke_default('form_errors', $obj_type, $object, $form, $form_state);
}
}
......
......@@ -17,16 +17,16 @@ function field_default_form($obj_type, $object, $field, $instance, $langcode, $i
}
$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['field'][$field_name][$langcode] = array(
'field' => $field,
'instance' => $instance,
// This entry will be populated at form build time.
'array_parents' => array(),
// This entry will be populated at form validation time.
'errors' => array(),
);
// Populate widgets with default values when creating a new object.
......@@ -34,13 +34,13 @@ function field_default_form($obj_type, $object, $field, $instance, $langcode, $i
$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 +53,7 @@ function field_default_form($obj_type, $object, $field, $instance, $langcode, $i
'#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 +68,17 @@ function field_default_form($obj_type, $object, $field, $instance, $langcode, $i
// 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 +90,34 @@ function field_default_form($obj_type, $object, $field, $instance, $langcode, $i
'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);
// Populate the 'array_parents' information in $form_state['field'] after
// the form is built, so that we catch changes in the form structure performed
// in alter() hooks.
$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;
}
......@@ -158,7 +158,7 @@ function field_multiple_value_form($field, $instance, $langcode, $items, &$form,
$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_form';
......@@ -169,6 +169,7 @@ function field_multiple_value_form($field, $instance, $langcode, $items, &$form,
'#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,
......@@ -304,36 +305,53 @@ function theme_field_multiple_value_form($variables) {
return $output;
}
/**
* Stores information about the built form structure in the form storage.
*
* 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['field'][$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['field'][$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);
}
}
}
......@@ -374,22 +392,25 @@ function field_add_more_submit($form, &$form_state) {
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['field'][$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'] = '<div class="ajax-new-content">' . (isset($field_form[$langcode][$delta]['#prefix']) ? $field_form[$langcode][$delta]['#prefix'] : '');
$field_form[$langcode][$delta]['#suffix'] = (isset($field_form[$langcode][$delta]['#suffix']) ? $field_form[$langcode][$delta]['#suffix'] : '') . '</div>';
$delta = $element['#max_delta'];
$element[$delta]['#prefix'] = '<div class="ajax-new-content">' . (isset($element[$delta]['#prefix']) ? $element[$delta]['#prefix'] : '');
$element[$delta]['#suffix'] = (isset($element[$delta]['#suffix']) ? $element[$delta]['#suffix'] : '') . '</div>';
return drupal_render($field_form);
return $element;
}
......@@ -337,8 +337,8 @@ function number_field_widget_form(&$form, &$form_state, $field, $instance, $lang
* FAPI validation of an individual number element.
*/
function number_field_widget_validate($element, &$form_state) {
$field = $form_state['complete form']['#fields'][$element['#field_name']]['field'];
$instance = $form_state['complete form']['#fields'][$element['#field_name']]['instance'];
$field = $form_state['field'][$element['#field_name']][$element['#language']]['field'];
$instance = $form_state['field'][$element['#field_name']][$element['#language']]['instance'];
$type = $element['#number_type'];
$value = $element['#value'];
......@@ -373,6 +373,6 @@ function number_field_widget_validate($element, &$form_state) {
/**
* Implements hook_field_widget_error().
*/
function number_field_widget_error($element, $error) {
function number_field_widget_error($element, $error, $form, &$form_state) {
form_error($element['value'], $error['message']);
}
......@@ -361,7 +361,7 @@ function options_array_flatten($array) {
/**
* Implements hook_field_widget_error().
*/
function options_field_widget_error($element, $error) {
function options_field_widget_error($element, $error, $form, &$form_state) {
form_error($element, $error['message']);
}
......
......@@ -566,7 +566,7 @@ function text_field_widget_form(&$form, &$form_state, $field, $instance, $langco
/**
* Implements hook_field_widget_error().
*/
function text_field_widget_error($element, $error) {
function text_field_widget_error($element, $error, $form, &$form_state) {
switch ($error['error']) {
case 'text_summary_max_length':
$error_element = $element[$element['#columns'][1]];
......
......@@ -1148,6 +1148,9 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase {
$values[1]['value'] = 0;
$langcode = LANGUAGE_NONE;
// Pretend the form has been built.
drupal_prepare_form('field_test_entity_form', $form, $form_state);
$form = form_builder('field_test_entity_form', $form, $form_state);
$form_state['values'] = array($this->field_name => array($langcode => $values));
field_attach_submit($entity_type, $entity, $form, $form_state);
......
......@@ -194,7 +194,7 @@ function field_test_widget_multiple_validate($element, &$form_state) {
/**
* Implements hook_field_widget_error().
*/
function field_test_field_widget_error($element, $error) {
function field_test_field_widget_error($element, $error, $form, &$form_state) {
// @todo No easy way to differenciate widget types, we should receive it as a
// parameter.
if (isset($element['value'])) {
......
......@@ -1212,10 +1212,6 @@ function field_ui_default_value_widget($field, $instance, &$form, &$form_state)
// @todo Allow multiple values (requires more work on 'add more' JS handler).
$element += field_default_form(NULL, NULL, $field, $instance, LANGUAGE_NONE, $items, $form, $form_state, 0);
// Adjust 'form_path' to reflect the actual location of the widget in the
// form structure.
$form['#fields'][$field_name]['form_path'] = array('instance', 'default_value_widget', $field_name);
return $element;
}
......@@ -1224,6 +1220,8 @@ function field_ui_default_value_widget($field, $instance, &$form, &$form_state)
*/
function field_ui_field_edit_form_validate($form, &$form_state) {
$instance = $form_state['values']['instance'];
$field_name = $instance['field_name'];
$field = field_info_field($field_name);
// Validate the default value.
if (!empty($instance['default_value_widget'])) {
......@@ -1240,8 +1238,9 @@ function field_ui_field_edit_form_validate($form, &$form_state) {
if (function_exists($function)) {
$function(NULL, NULL, $field, $instance, LANGUAGE_NONE, $items, $errors);
}
if ($errors) {
field_default_form_errors(NULL, NULL, $field, $instance, LANGUAGE_NONE, $items, $form, $errors);
if (isset($errors[$field_name][LANGUAGE_NONE])) {
$form_state['field'][$field_name][LANGUAGE_NONE]['errors'] = $errors[$field_name][LANGUAGE_NONE];
field_default_form_errors(NULL, NULL, $field, $instance, LANGUAGE_NONE, $items, $form, $form_state);
}
}
}
......
......@@ -492,9 +492,9 @@ function file_field_widget_form(&$form, &$form_state, $field, $instance, $langco
$elements['#process'] = array('file_field_widget_process_multiple');
$elements['#title'] = $element['#title'];
$elements['#description'] = $element['#description'];
$elements['#object_type'] = $element['#object_type'];
$elements['#bundle'] = $element['#bundle'];
$elements['#field_name'] = $element['#field_name'];
$elements['#language'] = $element['#language'];
$elements['#display_field'] = $field['settings']['display_field'];
// Add some properties that will eventually be added to the file upload
// field. These are added here so that they may be referenced easily through
......@@ -562,7 +562,7 @@ function file_field_widget_value($element, $input = FALSE, $form_state) {
if ($input) {
// Checkboxes lose their value when empty.
// If the display field is present make sure its unchecked value is saved.
$field = field_info_field($element['#field_name']);
$field = $form_state['field'][$element['#field_name']][$element['#language']]['field'];
if (empty($input['display'])) {
$input['display'] = $field['settings']['display_field'] ? 0 : 1;
}
......@@ -590,8 +590,8 @@ function file_field_widget_process($element, &$form_state, $form) {
$item = $element['#value'];
$item['fid'] = $element['fid']['#value'];
$field = field_info_field($element['#field_name']);
$instance = field_info_instance($element['#object_type'], $element['#field_name'], $element['#bundle']);
$field = $form_state['field'][$element['#field_name']][$element['#language']]['field'];
$instance = $form_state['field'][$element['#field_name']][$element['#language']]['instance'];
$settings = $instance['widget']['settings'];
$element['#theme'] = 'file_widget';
......@@ -702,9 +702,6 @@ function theme_file_widget($variables) {
function theme_file_widget_multiple($variables) {
$element = $variables['element'];
$field = field_info_field($element['#field_name']);
$instance = field_info_instance($element['#object_type'], $element['#field_name'], $element['#bundle']);
// Get our list of widgets in order.
$widgets = array();
foreach (element_children($element) as $key) {
......@@ -719,7 +716,7 @@ function theme_file_widget_multiple($variables) {
// Build up a table of applicable fields.
$headers = array();
$headers[] = t('File information');
if (!empty($field['settings']['display_field'])) {
if ($element['#display_field']) {
$headers[] = array(
'data' => t('Display'),
'class' => array('checkbox'),
......@@ -747,7 +744,7 @@ function theme_file_widget_multiple($variables) {
// Render the "Display" option in its own own column.
$display = '';
if (!empty($field['settings']['display_field'])) {
if ($element['#display_field']) {
unset($element[$key]['display']['#title']);
$display = array(
'data' => drupal_render($element[$key]['display']),
......@@ -765,7 +762,7 @@ function theme_file_widget_multiple($variables) {
$row = array();
$row[] = $information;
if (!empty(