summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/field/field.api.php44
-rw-r--r--modules/field/field.attach.inc101
-rw-r--r--modules/field/field.autoload.inc53
-rw-r--r--modules/field/field.crud.inc5
-rw-r--r--modules/field/field.default.inc54
-rw-r--r--modules/field/field.form.inc40
-rw-r--r--modules/field/field.module9
-rw-r--r--modules/field/field.test21
-rw-r--r--modules/field/modules/list/list.module20
-rw-r--r--modules/field/modules/number/number.module43
-rw-r--r--modules/field/modules/options/options.module8
-rw-r--r--modules/field/modules/text/text.module45
-rw-r--r--modules/field/modules/text/text.test25
-rw-r--r--modules/node/node.module3
-rw-r--r--modules/node/node.pages.inc8
-rw-r--r--modules/simpletest/tests/field_test.module37
-rw-r--r--modules/user/user.module8
-rw-r--r--modules/user/user.pages.inc19
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();