Hi,

On my site I would like the users to be able to fill in a create node form with values 1-10, and then have those numbers plugged into a linear regression formula prior to having them written in to the database.
I was advised in the IRC channel to use the hook_field_presave to manipulate the value before it has been written.
At the bottom of this post is a very simple module (first one I've ever tried to write) I put together using the hook_field_presave which attempts to multiple all field values by 10 - The machine name of the node is performance.
Unfortunately this module is having no effect on the field values. Can anybody point out what I'm doing wrong?

Thanks,
Paul

<?php
/**
* @file
* Takes the test values and converts them in to scores based on regression formula
*/

/**
* Implements hook_field_presave() to edit field value.
*/
function test_calculator_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {

// Take the field value and multiply by 10
foreach ($items as $performance => $item) {
if (isset($item['value'])) {
$items[$performance]['value'] = $item['value']*10;
}
}
}

/**
* Use Devel to print field value to "drupal_debug.txt"
*/
dd($items);

Comments

agileadam’s picture

I haven't really verified any of the code, but you'll at least want to move dd();

/**
 * @file
 * Takes the test values and converts them in to scores based on regression formula
 */

/**
 * Implements hook_field_presave() to edit field value.
 */
function test_calculator_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
  // Take the field value and multiply by 10
  foreach ($items as $performance => $item) {
    if (isset($item['value'])) {
      $items[$performance]['value'] = $item['value']*10;
    }
  }
  dd($items);
}
paulfield05’s picture

Thanks!
Unfortunately Devel isn't actually writing anything which suggests a problem with my module...
Hoepfully someone can tell me where I am going wrong :)

Paul

beanluc’s picture

hook_field_presave() only operates on fields which the same module provides.

For example: taxonomy.module implements taxonomy fields, and, taxonomy_field_presave() operates on those fields and no others.

In order to accomplish this in fields which your modue does not implement, use hook_form_FORM_ID_alter() to attach a validation function to the field in question, and use the validation function to update the field's value before the form is saved.

paulfield05’s picture

Thanks for all your help beanluc, as a result, I have managed to put together the following module which works:
------------------------------------------------------------------------------------------------------------------------
// hook_form_alter, check so it's the correct form and attach a validate function

function tcalc_form_alter(&$form,&$form_state,$form_id) {
if($form_id == "performance_node_form") {
$form['#submit']['#validate'] = 'performance_node_form_validate';
}
}

// validate function, which just sets the `field_performance_agility` field to 10 using `form_set_value`

function performance_node_form_validate($form, &$form_state) {
$form['field_performance_agility']['#parents'] = array('field_performance_agility');
form_set_value($form['field_performance_agility'], array('und' => array(0 => array('value' => 20))), $form_state);
}

------------------------------------------------------------------------------------------------------------------------

What I need to do now is to rather than overide the field value, pass it in to a formula.
How can i create a variable and assign it the field values name? This, for example, would allow me to have something like: array('value' => 2 * field_variable + 100)

Many Thanks,
Paul

beanluc’s picture

I recommend something like

/*
 * Implements hook_form_FORM_ID_alter().
 *
 * Attach a validate function so that value can be re-calculated and updated.
 */
function tcalc_form_performance_node_form_alter(&$form,&$form_state,$form_id) {
  $form['#submit']['#validate'] = 'performance_node_form_validate';
}

/*
 * Implements a form validation function.
 * 
 * Updates the field value after recalculating a new value.
 */
function performance_node_form_validate($form, &$form_state) {
  // Create a variable to pass in to form_set_value() later.
  $newvalue = _performance_node_form_multiply_by_ten($form_state['field_performance_agility']['und'][0]['#value']);
  $form['field_performance_agility']['#parents'] = array('field_performance_agility');
  form_set_value($form['field_performance_agility'], array('und' => array(0 => array('value' => $newvalue))), $form_state);
}

/*
 * A utility function which multiplies argument by ten.
 */
function _performance_node_form_multiply_by_ten($value) {
  return ( (int) $value *10 );
}
paulfield05’s picture

Thanks for the sample code - I am up and running now! :)
One thing I did do differently to your example, is pulling out values from the form. In your example you used:

$form_state['field_performance_agility']['und'][0]['#value']

When I followed this structure, I got an undefined index error. So played around and found the following worked for me:

$form_state['values']['field_performance_agility']['und']['0']['value']

To be honest I am a bit confused about the associative array structure - I would expect the code you gave to work from what I know about php arrays.... Any idea why i would have to use ['values'] as a prefix to the usual associative array? Also, when I used a hash to prefix 'value':

$form_state['values']['field_performance_agility']['und']['0']['#value']

I got the same undefined index error....I understand # is used to define an attribute(?) so i see why you've used it.

Thanks.
Paul

beanluc’s picture

Any idea why i would have to use ['values'] as a prefix to the usual associative array?

It's not a prefix, it's part of the actual array.

Apparently, it's the only part of the array which will actually be handled the way we need here.

# is used to define an attribute(?)

Sort of. In this case, "attribute" means to Drupal, some meta-information about the field. The data itself isn't exactly an "attribute" of the field, it's more like a hard property. So, simply by convention (and the API definition), the # isn't quite suitable for the "value" part of the array.

I recommend you look into using the Devel module and its dpm() function. Running dpm($form_state); would go an awful long way toward helping a developer zero in on the precise structure and names necessary for the task. If you aren't familiar with what that is and what it can do for you, take a quick look at https://www.google.com/search?q=drupal+devel+dpm&tbm=vid