Hi -
I'm porting a module to D7/Fields and having a bit of trouble with the hook_field_validate().
I would expect that I could change $items and have that change the actual value, but I can't. Even though I change $items (and the values look good) it's still the same old $items when we get to hook_field_format_views().
The use case: People enter one of several formats for the ASIN. They get converted in hook_field_validate(), which then updates the $items array (wishfully).
The code is below. Am I doing something crazy? It seems like most form functions allow updating.
function asin_field_validate($entity_type, $entity, $field, $instance, $langcode, &$items, &$errors) {
foreach ($items as $delta => &$item) {
$asin = $item['asin'];
if (!empty($asin)) { // Only continue if the ASIN is populated.
$results = _asin_load_items($asin);
if (!empty($results)) {
$item['asin'] = key($results);
}
else {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => t('No Amazon product with the ASIN "%id" could be located.', array('%id' => $item['asin'])),
);
}
}
}
}
Comments
Comment #1
yched commentedfield_attach_form_validate() and field_attach_submit() both start with fresh $items extracted from $form_state['values'], so the $items you change in validate don't live up to submit time.
For that matter, one level up, node_form_validate() itself builds a "$node", runs node_validate() and field_attach_form_validate() on it, then ditches it as well. The $items you'd alter in hook_field_validate() end up in a $node that is thrown away in node module.
Alterations need to happen in hook_field_presave().
More globally, I might get it wrong, but your code seems to assume an incoming $item['asin'] in a different format that the one that gets stored in the end (since the code needs to do some 'translation' with
$item['asin'] = key($results)).If that's the case, then the check and the 'translation' should happen in a FAPI #validate_callback set by the widget. It's the given widget type that allows a given user-friendly input format and has to handle it. A different (imaginary) widget might have different needs and input format, hook_field_validate() cannot handle all cases for widgets it doesn't know about.
hook_field_validate() comes after FAPI validation, and should only validate data in the 'canonical' format that gets stored and loaded.
You should be able provide f_a_validate() with a freshly loaded node :
But, again, I might get your code wrong, and it's possibly correct as is.
+
'error' => t('No Amazon ...')should be'message' => t('No Amazon ...'), and 'error' should be an error code (an arbitrary machine-name string that makes sense for the error - 'amazon_invalid_product' or something)Comment #2
rfayThanks so much for the help! That's top quality. I'll give it a shot today.
Comment #3
rfayThanks very much for your help.
I did a #element_validate on the FAPI textfield. It seemed a bit awkward, but does in fact seem to work correctly. Any other ways I could have done this with FAPI? (Thanks for the suggestions above on how to do it with hook_field_presave()).
Comment #4
yched commentedNope, that's exactly the idea.
Only thing is to use form_error($element) instead of form_set_error("hard_to_find_element_name")
and form_set_value($element) instead of directly writing to $form_state['values'].
See how number_field_widget_validate() does (I should probably have pointed you to that function earlier :-/).
Also note that number_field_widget_validate() (widget-level validation as a FAPI callback) only checks that the user input is well formed.
number.module's hook_field_validate() (field-level validatation) does the generic, widget-agnostic checks (min and max value...).
Once you get that, you grok the (admittedly tricky) separation between widget validation and field validation ;-).