Commit eecab108 authored by webchick's avatar webchick

#369964 by yched and bjaspan: Refactor field validation and error reporting....

#369964 by yched and bjaspan: Refactor field validation and error reporting. Field API no longer coupled to Form API. Hooray.
parent 87f82a61
......@@ -215,11 +215,25 @@ function hook_field_load($obj_type, $object, $field, $instance, $items) {
* The instance structure for $field on $object's bundle.
* @param $items
* $object->{$field['field_name']}, or an empty array if unset.
* @param $form
* The form structure being validated. NOTE: This parameter will
* become obsolete (see field_attach_validate()).
*/
function hook_field_validate($obj_type, $object, $field, $instance, $items, $form) {
* @param $errors
* The array of errors, keyed by field name and by value delta, that have
* already been reported for the object. The function should add its errors
* to this array. Each error is an associative array, with the following
* keys and values:
* - 'error': an error code (should be a string, prefixed with the module name)
* - 'message': the human readable message to be displayed.
*/
function hook_field_validate($obj_type, $object, $field, $instance, $items, &$errors) {
foreach ($items as $delta => $item) {
if (!empty($item['value'])) {
if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) {
$errors[$field['field_name']][$delta][] = array(
'error' => 'text_max_length',
'message' => t('%name: the value may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])),
);
}
}
}
}
/**
......@@ -388,6 +402,24 @@ function hook_field_widget(&$form, &$form_state, $field, $instance, $items, $del
return $element;
}
/**
* Flag a field-level validation error.
*
* @param $element
* An array containing the form element for the widget. The error needs to be
* flagged on the right sub-element, according to the widget's internal
* structure.
* @param $error
* An associative array with the following key-value pairs, as returned by
* hook_field_validate():
* - '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.
*/
function hook_field_widget_error($element, $error) {
form_error($element['value'], $error['message']);
}
/**
* @} End of "ingroup field_type"
*/
......@@ -425,7 +457,7 @@ function hook_field_attach_load($obj_type, $object) {
*
* See field_attach_validate() for details and arguments.
*/
function hook_field_attach_validate($obj_type, $object, &$form) {
function hook_field_attach_validate($obj_type, $object, &$errors) {
}
/**
......
......@@ -12,6 +12,31 @@
// and others process the combined value of all fields.
// Should all iteration through available fields be done here instead of in Field?
/**
* Exception class thrown by field_attach_validate() when field
* validation errors occur.
*/
class FieldValidationException extends FieldException {
var $errors;
/**
* Constructor for FieldValidationException.
*
* @param $errors
* An array of field validation errors, keyed by field name and
* delta that contains two keys:
* - 'error': A machine-readable error code string, prefixed by
* the field module name. A field widget may use this code to decide
* how to report the error.
* - 'message': A human-readable error message such as to be
* passed to form_error() for the appropriate form element.
*/
function __construct($errors) {
$this->errors = $errors;
parent::__construct(t('Field validation errors'));
}
}
/**
* @defgroup field_storage Field Storage API
* @{
......@@ -131,10 +156,6 @@ function _field_invoke($op, $obj_type, &$object, &$a = NULL, &$b = NULL, $defaul
$field = field_info_field($field_name);
$items = isset($object->$field_name) ? $object->$field_name : array();
// Make sure AHAH 'add more' button isn't sent to the fields for processing.
// TODO D7 : needed ?
unset($items[$field_name . '_add_more']);
$function = $default ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
if (drupal_function_exists($function)) {
$result = $function($obj_type, $object, $field, $instance, $items, $a, $b);
......@@ -306,29 +327,76 @@ function _field_attach_load_revision($obj_type, $objects) {
/**
* Perform field validation against the field data in an object.
* Field validation is distinct from widget validation; the latter
* occurs during the Form API validation phase.
*
* NOTE: This functionality does not yet exist in its final state.
* Eventually, field validation will occur during field_attach_insert
* or _update which will throw an exception on failure. For now,
* fieldable entities must call this during their Form API validation
* phase, and field validation will call form_set_error for any
* errors. See http://groups.drupal.org/node/18019.
* This function does not perform field widget validation on form
* submissions. It is intended to be called during API save
* operations. Use field_attach_form_validate() to validate form
* submissions.
*
* @param $obj_type
* The type of $object; e.g. 'node' or 'user'.
* @param $object
* The object with fields to validate.
* @return
* Throws a FieldValidationException if validation errors are found.
*/
function _field_attach_validate($obj_type, &$object, $form = NULL) {
_field_invoke('validate', $obj_type, $object, $form);
_field_invoke_default('validate', $obj_type, $object, $form);
function _field_attach_validate($obj_type, &$object) {
$errors = array();
_field_invoke_default('validate', $obj_type, $object, $errors);
_field_invoke('validate', $obj_type, $object, $errors);
// Let other modules validate the object.
foreach (module_implements('field_attach_validate') as $module) {
$function = $module . '_field_attach_validate';
$function($obj_type, $object, $form);
$function($obj_type, $object, $errors);
}
if ($errors) {
throw new FieldValidationException($errors);
}
}
/**
* Perform field validation against form-submitted field values.
*
* There are two levels of validation for fields in forms: widget
* validation, and field validation.
* - Widget validation steps are specific to a given widget's own form
* structure and UI metaphors. They are executed through FAPI's
* #element_validate property during normal form validation.
* - Field validation steps are common to a given field type, independently of
* the specific widget being used in a given form. They are defined in the
* field type's implementation of hook_field_validate().
*
* This function performs field validation in the context of a form
* submission. It converts field validation errors into form errors
* on the correct form elements. Fieldable object types should call
* this function during their own form validation function.
*
* @param $obj_type
* The type of $object; e.g. 'node' or 'user'.
* @param $object
* The object being submitted. The 'bundle key', 'id key' and (if applicable)
* 'revision key' should be present. The actual field values will be read
* from $form_state['values'].
* @param $form
* The form structure.
* @param $form_state
* An associative array containing the current state of the form.
*/
function _field_attach_form_validate($obj_type, &$object, $form, &$form_state) {
// Extract field values from submitted values.
_field_invoke_default('extract_form_values', $obj_type, $object, $form, $form_state);
// Perform field_level validation.
try {
field_attach_validate($obj_type, $object);
}
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);
}
}
......@@ -350,6 +418,9 @@ function _field_attach_validate($obj_type, &$object, $form = NULL) {
* An associative array containing the current state of the form.
*/
function _field_attach_submit($obj_type, &$object, $form, &$form_state) {
// Extract field values from submitted values.
_field_invoke_default('extract_form_values', $obj_type, $object, $form, $form_state);
_field_invoke_default('submit', $obj_type, $object, $form, $form_state);
// Let other modules act on submitting the object.
......
......@@ -87,26 +87,59 @@ function field_attach_load_revision($obj_type, $objects) {
/**
* Perform field validation against the field data in an object.
* Field validation is distinct from widget validation; the latter
* occurs during the Form API validation phase.
*
* NOTE: This functionality does not yet exist in its final state.
* Eventually, field validation will occur during field_attach_insert
* or _update which will throw an exception on failure. For now,
* fieldable entities must call this during their Form API validation
* phase, and field validation will call form_set_error for any
* errors. See http://groups.drupal.org/node/18019.
* This function does not perform field widget validation on form
* submissions. It is intended to be called during API save
* operations. Use field_attach_form_validate() to validate form
* submissions.
*
* @param $obj_type
* The type of $object; e.g. 'node' or 'user'.
* @param $object
* The object with fields to validate.
* @return
* Throws a FieldValidationException if validation errors are found.
*
* This function is an autoloader for _field_attach_validate() in modules/field/field.attach.inc.
*/
function field_attach_validate($obj_type, &$object, $form = NULL) {
function field_attach_validate($obj_type, &$object) {
require_once DRUPAL_ROOT . '/modules/field/field.attach.inc';
return _field_attach_validate($obj_type, $object);
}
/**
* Perform field validation against form-submitted field values.
*
* There are two levels of validation for fields in forms: widget
* validation, and field validation.
* - Widget validation steps are specific to a given widget's own form
* structure and UI metaphors. They are executed through FAPI's
* #element_validate property during normal form validation.
* - Field validation steps are common to a given field type, independently of
* the specific widget being used in a given form. They are defined in the
* field type's implementation of hook_field_validate().
*
* This function performs field validation in the context of a form
* submission. It converts field validation errors into form errors
* on the correct form elements. Fieldable object types should call
* this function during their own form validation function.
*
* @param $obj_type
* The type of $object; e.g. 'node' or 'user'.
* @param $object
* The object being submitted. The 'bundle key', 'id key' and (if applicable)
* 'revision key' should be present. The actual field values will be read
* from $form_state['values'].
* @param $form
* The form structure.
* @param $form_state
* An associative array containing the current state of the form.
*
* This function is an autoloader for _field_attach_form_validate() in modules/field/field.attach.inc.
*/
function field_attach_form_validate($obj_type, &$object, $form, &$form_state) {
require_once DRUPAL_ROOT . '/modules/field/field.attach.inc';
return _field_attach_validate($obj_type, $object, $form);
return _field_attach_form_validate($obj_type, $object, $form, $form_state);
}
/**
......
......@@ -6,11 +6,6 @@
* Field CRUD API, handling field and field instance creation and deletion.
*/
/**
* TODO: Fill me in.
*/
class FieldException extends Exception {}
/**
* @defgroup field_structs Field API data structures
* @{
......
......@@ -10,46 +10,44 @@
* types. Those functions take care of default stuff common to all field types.
*/
function field_default_validate($obj_type, $object, $field, $instance, $items, $form) {
// TODO: here we could validate that required fields are filled in (for programmatic save)
}
function field_default_submit($obj_type, &$object, $field, $instance, &$items, $form, &$form_state) {
// Get field values from the submitted form values. Assigning them to $items
// populates $object->field_name when we return from _field_invoke_default().
// TODO D7: Allow the values to be form_altered to another location, like we
// do for the form definition ($form['#fields'][$field_name]['form_path']) ?
function field_default_extract_form_values($obj_type, $object, $field, $instance, &$items, $form, &$form_state) {
$field_name = $field['field_name'];
if (isset($form_state['values'][$field['field_name']])) {
$items = $form_state['values'][$field['field_name']];
if (isset($form_state['values'][$field_name])) {
$items = $form_state['values'][$field_name];
// Remove the 'value' of the 'add more' button.
unset($items[$field['field_name'] . '_add_more']);
// TODO: the above should be moved to validate time (and values saved back
// using form_set_value() ), so that hook_field_validate() works on clean data.
// Not sure we'll want what's below in validate too.
// Reorder items to account for drag-n-drop reordering.
if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
$items = _field_sort_items($field, $items);
}
// Filter out empty values.
$items = field_set_empty($field, $items);
unset($items[$field_name . '_add_more']);
// _field_invoke() does not add back items for fields not present in the
// original $object, so add them manually.
$object->{$field['field_name']} = $items;
// original $object, so we add them manually.
$object->{$field_name} = $items;
}
else {
// The form did not include this field, for instance because of access
// rules: make sure any existing value for the field stays unchanged.
unset($object->{$field['field_name']});
unset($object->{$field_name});
}
}
function field_default_validate($obj_type, $object, $field, $instance, $items) {
// TODO: here we could validate that required fields are filled in (for programmatic save)
}
function field_default_submit($obj_type, &$object, $field, $instance, &$items, $form, &$form_state) {
$field_name = $field['field_name'];
// TODO: should me move what's below to __extract_form_values ?
// Reorder items to account for drag-n-drop reordering.
if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
$items = _field_sort_items($field, $items);
}
// Filter out empty values.
$items = field_set_empty($field, $items);
}
/**
* The 'view' operation constructs the $object in a way that you can use
* drupal_render() to display the formatted output for an individual field.
......
......@@ -284,6 +284,42 @@ function theme_field_multiple_value_form($element) {
return $output;
}
/**
* Transfer field-level validation errors to widgets.
*/
function field_default_form_errors($obj_type, $object, $field, $instance, $items, $form, $errors) {
$field_name = $field['field_name'];
if (!empty($errors[$field_name])) {
$function = $instance['widget']['module'] . '_field_widget_error';
$function_exists = drupal_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) {
$element = $element[$key];
}
$multiple_widget = field_behaviors_widget('multiple values', $instance) != FIELD_BEHAVIOR_DEFAULT;
foreach ($errors[$field_name] 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 : $element[$delta];
foreach ($delta_errors as $error) {
if ($function_exists) {
$function($error_element, $error);
}
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']);
}
}
}
}
}
/**
* Submit handler to add more choices to a field form. This handler is used when
* JavaScript is not available. It makes changes to the form state and the
......@@ -292,11 +328,11 @@ function theme_field_multiple_value_form($element) {
function field_add_more_submit($form, &$form_state) {
// Set the form to rebuild and run submit handlers.
if (isset($form['#builder_function']) && drupal_function_exists($form['#builder_function'])) {
$form['#builder_function']($form, $form_state);
$entity = $form['#builder_function']($form, $form_state);
// Make the changes we want to the form state.
$field_name = $form_state['clicked_button']['#field_name'];
if ($form_state['values'][$field_name][$field_name . '_add_more']) {
if ($form_state['values'][$field_name . '_add_more']) {
$form_state['field_item_count'][$field_name] = count($form_state['values'][$field_name]);
}
}
......
......@@ -78,6 +78,15 @@
*/
define('FIELD_LOAD_REVISION', 'FIELD_LOAD_REVISION');
/**
* Base class for all exceptions thrown by Field API functions.
*
* This class has no functionality of its own other than allowing all
* Field API exceptions to be caught by a single catch block.
*/
class FieldException extends Exception {}
/**
* Implementation of hook_flush_caches.
*/
......
......@@ -517,10 +517,6 @@ class FieldAttachTestCase extends DrupalWebTestCase {
// Verify that field_attach_validate() invokes the correct
// hook_field_validate.
// TODO: This tests the FAPI-connected behavior of hook_field_validate.
// As discussed at http://groups.drupal.org/node/18019, field validation
// will eventually be disconnected from FAPI, at which point this
// function will have to be rewritten.
function testFieldAttachValidate() {
$entity_type = 'test_entity';
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
......@@ -535,19 +531,24 @@ class FieldAttachTestCase extends DrupalWebTestCase {
$values[1]['value'] = 1;
$entity->{$this->field_name} = $values;
field_attach_validate($entity_type, $entity, array());
try {
field_attach_validate($entity_type, $entity);
}
catch (FieldValidationException $e) {
$errors = $e->errors;
}
$errors = form_get_errors();
foreach ($values as $delta => $value) {
if ($value['value'] != 1) {
$this->assertTrue(isset($errors[$value['_error_element']]), "Error is set on {$value['_error_element']}: {$errors[$value['_error_element']]}");
unset($errors[$value['_error_element']]);
$this->assertIdentical($errors[$this->field_name][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta");
$this->assertEqual(count($errors[$this->field_name][$delta]), 1, "Only one error set on value $delta");
unset($errors[$this->field_name][$delta]);
}
else {
$this->assertFalse(isset($errors[$value['_error_element']]), "Error is not set on {$value['_error_element']}");
$this->assertFalse(isset($errors[$this->field_name][$delta]), "No error set on value $delta");
}
}
$this->assertEqual(count($errors), 0, 'No extraneous form errors set');
$this->assertEqual(count($errors[$this->field_name]), 0, 'No extraneous errors set');
}
// Validate that FAPI elements are generated. This could be much
......
......@@ -94,17 +94,19 @@ function list_field_columns($field) {
/**
* Implementation of hook_field_validate().
*
* Possible error codes:
* - 'list_illegal_value': The value is not part of the list of allowed values.
*/
function list_field_validate($obj_type, $object, $field, $instance, $items, $form) {
function list_field_validate($obj_type, $object, $field, $instance, $items, &$errors) {
$allowed_values = list_allowed_values($field);
if (is_array($items)) {
foreach ($items as $delta => $item) {
$error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
if (!empty($item['value'])) {
if (count($allowed_values) && !array_key_exists($item['value'], $allowed_values)) {
form_set_error($error_element, t('%name: illegal value.', array('%name' => t($instance['label']))));
}
foreach ($items as $delta => $item) {
if (!empty($item['value'])) {
if (count($allowed_values) && !array_key_exists($item['value'], $allowed_values)) {
$errors[$field['field_name']][$delta][] = array(
'error' => 'list_illegal_value',
'message' => t('%name: illegal value.', array('%name' => t($instance['label']))),
);
}
}
}
......
......@@ -84,19 +84,25 @@ function number_field_columns($field) {
/**
* Implementation of hook_field_validate().
*
* Possible error codes:
* - 'number_min': The value is smaller than the allowed minimum value.
* - 'number_max': The value is larger than the allowed maximum value.
*/
function number_field_validate($obj_type, $node, $field, $instance, &$items, $form) {
if (is_array($items)) {
foreach ($items as $delta => $item) {
$error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
if ($item['value'] != '') {
if (is_numeric($instance['settings']['min']) && $item['value'] < $instance['settings']['min']) {
form_set_error($error_element, t('%name: the value may be no smaller than %min.', array('%name' => t($instance['label']), '%min' => $instance['settings']['min'])));
}
if (is_numeric($instance['settings']['max']) && $item['value'] > $instance['settings']['max']) {
form_set_error($error_element, t('%name: the value may be no larger than %max.', array('%name' => t($instance['label']), '%max' => $instance['settings']['max'])));
}
function number_field_validate($obj_type, $node, $field, $instance, $items, &$errors) {
foreach ($items as $delta => $item) {
if ($item['value'] != '') {
if (is_numeric($instance['settings']['min']) && $item['value'] < $instance['settings']['min']) {
$errors[$field['field_name']][$delta][] = array(
'error' => 'number_min',
'message' => t('%name: the value may be no smaller than %min.', array('%name' => t($instance['label']), '%min' => $instance['settings']['min'])),
);
}
if (is_numeric($instance['settings']['max']) && $item['value'] > $instance['settings']['max']) {
$errors[$field['field_name']][$delta][] = array(
'error' => 'number_max',
'message' => t('%name: the value may be no larger than %max.', array('%name' => t($instance['label']), '%max' => $instance['settings']['max'])),
);
}
}
}
......@@ -270,6 +276,13 @@ function number_field_widget(&$form, &$form_state, $field, $instance, $items, $d
return $element;
}
/**
* Implementation of hook_field_widget_error().
*/
function number_field_widget_error($element, $error) {
form_error($element['value'], $error['message']);
}
/**
* Process an individual element.
*
......@@ -333,12 +346,6 @@ function number_process($element, $edit, $form_state, $form) {
break;
}
// Used so that hook_field('validate') knows where to flag an error.
$element['_error_element'] = array(
'#type' => 'value',
'#value' => implode('][', array_merge($element['#parents'], array($field_key))),
);
return $element;
}
......
......@@ -109,6 +109,14 @@ function options_field_widget(&$form, &$form_state, $field, $instance, $items, $
return $element;
}
/**
* Implementation of hook_field_widget_error().
*/
function options_field_widget_error($element, $error) {
$field_key = $element['#columns'][0];
form_error($element[$field_key], $error['message']);
}
/**
* Process an individual element.
*
......
......@@ -88,16 +88,18 @@ function text_field_columns($field) {
/**
* Implementation of hook_field_validate().
*
* Possible error codes:
* - 'text_max_length': The value exceeds the maximum length.
*/
function text_field_validate($obj_type, $object, $field, $instance, $items, $form) {
if (is_array($items)) {
foreach ($items as $delta => $item) {
$error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
if (!empty($item['value'])) {
if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) {
form_set_error($error_element, t('%name: the value may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])));
}
function text_field_validate($obj_type, $object, $field, $instance, $items, &$errors) {
foreach ($items as $delta => $item) {
if (!empty($item['value'])) {
if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) {
$errors[$field['field_name']][$delta][] = array(
'error' => 'text_max_length',
'message' => t('%name: the value may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])),
);
}
}
}
......@@ -283,6 +285,13 @@ function text_field_widget(&$form, &$form_state, $field, $instance, $items, $del
return $element;
}
/**
* Implementation of hook_field_widget_error().
*/
function text_field_widget_error($element, $error) {
form_error($element['value'], $error['message']);
}
/**
* Process an individual element.
*
......@@ -328,13 +337,6 @@ function text_textfield_process($element, $edit, $form_state, $form) {
$element[$filter_key] = filter_form($format, 1, $parents);
}
// Used so that hook_field('validate') knows where to flag an error.
// TODO: rework that. See http://groups.drupal.org/node/18019.
$element['_error_element'] = array(
'#type' => 'value',
'#value' => implode('][', array_merge($element['#parents'], array($field_key))),
);
return $element;
}
......@@ -368,18 +370,11 @@ function text_textarea_process($element, $edit, $form_state, $form) {
);
if (!empty($instance['settings']['text_processing'])) {
$filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
$filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
$format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
$parents = array_merge($element['#parents'] , array($filter_key));
$element[$filter_key] = filter_form($format, 1, $parents);
}
// Used so that hook_field('validate') knows where to flag an error.
$element['_error_element'] = array(
'#type' => 'value',
'#value' => implode('][', array_merge($element['#parents'], array($field_key))),
);
return $element;
}
/**
......@@ -399,4 +394,4 @@ function theme_text_textfield($element) {
function theme_text_textarea($element) {
return $element['#children'];
}
\ No newline at end of file
}
......@@ -16,6 +16,31 @@ class TextFieldTestCase extends DrupalWebTestCase {
parent::setUp('field', 'text', 'field_test');
}
// Test fields.
/**
* Test text field validation.
*/
function testTextFieldValidation() {
// Create a field with settings to validate.
$max_length = 3;
$field = $this->drupalCreateField('text', NULL, array('settings' => array('max_length' => $max_length)));
$this->instance = $this->drupalCreateFieldInstance($field['field_name'], 'text_textfield', 'text_default', FIELD_TEST_BUNDLE);
// Test valid and invalid values with field_attach_validate().
$entity = field_test_create_stub_entity(0, 0, FIELD_TEST_BUNDLE);
for ($i = 0; $i <= $max_length + 2; $i++) {
$entity->{$field['field_name']}[0]['value'] = str_repeat('x', $i);
try {
field_attach_validate('test_entity', $entity);
$this->assertTrue($i <= $max_length, "Length $i does not cause validation error when max_length is $max_length");
}
catch (FieldValidationException $e) {
$this->assertTrue($i > $max_length, "Length $i causes validation error when max_length is $max_length");
}
}
}
// Test widgets.
/**
......
......@@ -997,9 +997,6 @@ function node_validate($node, $form = array()) {
}
}