diff options
-rw-r--r-- | modules/field/field.api.php | 44 | ||||
-rw-r--r-- | modules/field/field.attach.inc | 101 | ||||
-rw-r--r-- | modules/field/field.autoload.inc | 53 | ||||
-rw-r--r-- | modules/field/field.crud.inc | 5 | ||||
-rw-r--r-- | modules/field/field.default.inc | 54 | ||||
-rw-r--r-- | modules/field/field.form.inc | 40 | ||||
-rw-r--r-- | modules/field/field.module | 9 | ||||
-rw-r--r-- | modules/field/field.test | 21 | ||||
-rw-r--r-- | modules/field/modules/list/list.module | 20 | ||||
-rw-r--r-- | modules/field/modules/number/number.module | 43 | ||||
-rw-r--r-- | modules/field/modules/options/options.module | 8 | ||||
-rw-r--r-- | modules/field/modules/text/text.module | 45 | ||||
-rw-r--r-- | modules/field/modules/text/text.test | 25 | ||||
-rw-r--r-- | modules/node/node.module | 3 | ||||
-rw-r--r-- | modules/node/node.pages.inc | 8 | ||||
-rw-r--r-- | modules/simpletest/tests/field_test.module | 37 | ||||
-rw-r--r-- | modules/user/user.module | 8 | ||||
-rw-r--r-- | modules/user/user.pages.inc | 19 |
18 files changed, 383 insertions, 160 deletions
diff --git a/modules/field/field.api.php b/modules/field/field.api.php index 57ec7fdd2..ef08b0b75 100644 --- a/modules/field/field.api.php +++ b/modules/field/field.api.php @@ -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'])), + ); + } + } + } } /** @@ -389,6 +403,24 @@ function hook_field_widget(&$form, &$form_state, $field, $instance, $items, $del } /** + * 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) { } /** diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc index 9f2c745cc..d52f9db2a 100644 --- a/modules/field/field.attach.inc +++ b/modules/field/field.attach.inc @@ -13,6 +13,31 @@ // 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 * @{ * Implement a storage engine for Field API data. @@ -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. diff --git a/modules/field/field.autoload.inc b/modules/field/field.autoload.inc index e7cfbeb8c..9d848f65e 100644 --- a/modules/field/field.autoload.inc +++ b/modules/field/field.autoload.inc @@ -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); } /** diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc index c0ef6d6cd..93f24bf56 100644 --- a/modules/field/field.crud.inc +++ b/modules/field/field.crud.inc @@ -7,11 +7,6 @@ */ /** - * TODO: Fill me in. - */ -class FieldException extends Exception {} - -/** * @defgroup field_structs Field API data structures * @{ * Represent Field API fields and instances. diff --git a/modules/field/field.default.inc b/modules/field/field.default.inc index 1d01286d4..98d7cc371 100644 --- a/modules/field/field.default.inc +++ b/modules/field/field.default.inc @@ -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. diff --git a/modules/field/field.form.inc b/modules/field/field.form.inc index 8703e852d..b331e1fba 100644 --- a/modules/field/field.form.inc +++ b/modules/field/field.form.inc @@ -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]); } } diff --git a/modules/field/field.module b/modules/field/field.module index 2f93cea22..8d19f47ed 100644 --- a/modules/field/field.module +++ b/modules/field/field.module @@ -78,6 +78,15 @@ define('FIELD_LOAD_CURRENT', 'FIELD_LOAD_CURRENT'); */ 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. */ diff --git a/modules/field/field.test b/modules/field/field.test index 1a348b02c..12eb854c3 100644 --- a/modules/field/field.test +++ b/modules/field/field.test @@ -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 diff --git a/modules/field/modules/list/list.module b/modules/field/modules/list/list.module index 2d8b3680a..d1761fdf4 100644 --- a/modules/field/modules/list/list.module +++ b/modules/field/modules/list/list.module @@ -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']))), + ); } } } diff --git a/modules/field/modules/number/number.module b/modules/field/modules/number/number.module index 28202758a..1e693dac0 100644 --- a/modules/field/modules/number/number.module +++ b/modules/field/modules/number/number.module @@ -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'])), + ); } } } @@ -271,6 +277,13 @@ function number_field_widget(&$form, &$form_state, $field, $instance, $items, $d } /** + * Implementation of hook_field_widget_error(). + */ +function number_field_widget_error($element, $error) { + form_error($element['value'], $error['message']); +} + +/** * Process an individual element. * * Build the form element. When creating a form using FAPI #process, @@ -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; } diff --git a/modules/field/modules/options/options.module b/modules/field/modules/options/options.module index 776035c64..26dff4dda 100644 --- a/modules/field/modules/options/options.module +++ b/modules/field/modules/options/options.module @@ -110,6 +110,14 @@ function options_field_widget(&$form, &$form_state, $field, $instance, $items, $ } /** + * 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. * * Build the form element. When creating a form using FAPI #process, diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module index 46b85229b..182956c89 100644 --- a/modules/field/modules/text/text.module +++ b/modules/field/modules/text/text.module @@ -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'])), + ); } } } @@ -284,6 +286,13 @@ function text_field_widget(&$form, &$form_state, $field, $instance, $items, $del } /** + * Implementation of hook_field_widget_error(). + */ +function text_field_widget_error($element, $error) { + form_error($element['value'], $error['message']); +} + +/** * Process an individual element. * * Build the form element. When creating a form using FAPI #process, @@ -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 +} diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test index e92cfc416..a7c35cecc 100644 --- a/modules/field/modules/text/text.test +++ b/modules/field/modules/text/text.test @@ -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. /** diff --git a/modules/node/node.module b/modules/node/node.module index b2b18ae53..62dacd490 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -997,9 +997,6 @@ function node_validate($node, $form = array()) { } } - // Validate fields - field_attach_validate('node', $node, $form); - // Do node-type-specific validation checks. node_invoke($node, 'validate', $form); node_invoke_node($node, 'validate', $form); diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc index e0481e0fe..b77c3801c 100644 --- a/modules/node/node.pages.inc +++ b/modules/node/node.pages.inc @@ -68,7 +68,13 @@ function node_add($type) { } function node_form_validate($form, &$form_state) { - node_validate($form_state['values'], $form); + $node = $form_state['values']; + node_validate($node, $form); + + // Field validation. Requires access to $form_state, so this cannot be + // done in node_validate() as it currently exists. + $node = (object)$node; + field_attach_form_validate('node', $node, $form, $form_state); } function node_object_prepare(&$node) { diff --git a/modules/simpletest/tests/field_test.module b/modules/simpletest/tests/field_test.module index 3772ad920..f82fec9b3 100644 --- a/modules/simpletest/tests/field_test.module +++ b/modules/simpletest/tests/field_test.module @@ -84,7 +84,7 @@ function field_test_fieldable_info() { } function field_test_create_bundle($bundle, $text) { - $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle')); + $bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle')); $bundles += array($bundle => $text); variable_set('field_test_bundles', $bundles); @@ -92,7 +92,7 @@ function field_test_create_bundle($bundle, $text) { } function field_test_rename_bundle($bundle_old, $bundle_new) { - $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle')); + $bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle')); $bundles[$bundle_new] = $bundles[$bundle_old]; unset($bundles[$bundle_old]); variable_set('field_test_bundles', $bundles); @@ -101,7 +101,7 @@ function field_test_rename_bundle($bundle_old, $bundle_new) { } function field_test_delete_bundle($bundle) { - $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle')); + $bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle')); unset($bundles[$bundle]); variable_set('field_test_bundles', $bundles); @@ -252,8 +252,8 @@ function field_test_entity_form(&$form_state, $entity) { * Validate handler for field_test_set_field_values(). */ function field_test_entity_form_validate($form, &$form_state) { - $entity = (object)$form_state['values']; - field_attach_validate('test_entity', $entity, $form); + $entity = field_test_create_stub_entity($form_state['values']['ftid'], $form_state['values']['ftvid'], $form_state['values']['fttype']); + field_attach_form_validate('test_entity', $entity, $form, $form_state); } /** @@ -336,19 +336,19 @@ function field_test_field_instance_settings($field_type) { /** * Implementation of hook_field_validate(). + * + * Possible error codes: + * - 'field_test_invalid': The value is invalid. */ -function field_test_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 ($item['value'] == -1) { - form_set_error($error_element, t('%name does not accept the value -1.', array('%name' => $instance['label']))); - } +function field_test_field_validate($obj_type, $object, $field, $instance, $items, &$errors) { + foreach ($items as $delta => $item) { + if ($item['value'] == -1) { + $errors[$field['field_name']][$delta][] = array( + 'error' => 'field_test_invalid', + 'message' => t('%name does not accept the value -1.', array('%name' => $instance['label'])), + ); } } - - return $items; } /** @@ -448,6 +448,13 @@ function field_test_field_widget(&$form, &$form_state, $field, $instance, $items } /** + * Implementation of hook_field_widget_error(). + */ +function field_test_field_widget_error($element, $error) { + form_error($element['value'], $error['message']); +} + +/** * Implementation of hook_field_formatter_info(). */ function field_test_field_formatter_info() { diff --git a/modules/user/user.module b/modules/user/user.module index 66cb92fa9..ac15ecdbe 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -485,8 +485,8 @@ function user_save($account, $edit = array(), $category = 'account') { } // Save Field data. - $obj = (object) $edit; - field_attach_update('user', $obj); + $object = (object) $edit; + field_attach_update('user', $object); // Refresh user object. $user = user_load($account->uid, TRUE); @@ -521,8 +521,8 @@ function user_save($account, $edit = array(), $category = 'account') { // Build the initial user object. $user = user_load($edit['uid'], TRUE); - $obj = (object) $edit; - field_attach_insert('user', $obj); + $object = (object) $edit; + field_attach_insert('user', $object); user_module_invoke('insert', $edit, $user, $category); diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc index f1fb2f42d..7a055eb97 100644 --- a/modules/user/user.pages.inc +++ b/modules/user/user.pages.inc @@ -266,13 +266,12 @@ function user_profile_form($form_state, $account, $category = 'account') { * Validation function for the user account and profile editing form. */ function user_profile_form_validate($form, &$form_state) { - // Validate field widgets. - $tmp_obj = (object) $form_state['values']; - field_attach_validate('user', $tmp_obj, $form, $form_state); - - user_module_invoke('validate', $form_state['values'], $form_state['values']['_account'], $form_state['values']['_category']); + $edit = (object)$form_state['values']; + field_attach_form_validate('user', $edit, $form, $form_state); + $edit = (array)$edit; + user_module_invoke('validate', $edit, $form_state['values']['_account'], $form_state['values']['_category']); // Validate input to ensure that non-privileged users can't alter protected data. - if ((!user_access('administer users') && array_intersect(array_keys($form_state['values']), array('uid', 'init', 'session'))) || (!user_access('administer permissions') && isset($form_state['values']['roles']))) { + if ((!user_access('administer users') && array_intersect(array_keys($edit), array('uid', 'init', 'session'))) || (!user_access('administer permissions') && isset($form_state['values']['roles']))) { watchdog('security', 'Detected malicious attempt to alter protected user fields.', array(), WATCHDOG_WARNING); // set this to a value type field form_set_error('category', t('Detected malicious attempt to alter protected user fields.')); @@ -287,9 +286,11 @@ function user_profile_form_submit($form, &$form_state) { $category = $form_state['values']['_category']; unset($form_state['values']['_account'], $form_state['values']['op'], $form_state['values']['submit'], $form_state['values']['cancel'], $form_state['values']['form_token'], $form_state['values']['form_id'], $form_state['values']['_category'], $form_state['values']['form_build_id']); - field_attach_submit('user', $account, $form, $form_state); - user_module_invoke('submit', $form_state['values'], $account, $category); - user_save($account, $form_state['values'], $category); + $edit = (object)$form_state['values']; + field_attach_submit('user', $edit, $form, $form_state); + $edit = (array)$edit; + user_module_invoke('submit', $edit, $account, $category); + user_save($account, $edit, $category); // Clear the page cache because pages can contain usernames and/or profile information: cache_clear_all(); |