background image
HomeRecent PostsDrupalSearchTagsRSSContactAboutAccount
Eric.London's picture

In this article, I'll address an issue that has surfaced on every recent project I've been working on: how to update nodes when a CCK field's allowed values list changes. For my projects, I implemented a variety of batch processes and drush scripts to solve this problem. But for this article, I decided to implement a form submit handler solution to automatically update nodes when the CCK field settings page is submitted. Please be careful when implementing code like this, it has potential to update a lot of nodes, and hit memory limitation issues depending on how many nodes need to be updated. As I mentioned before, I implemented a combination of batch APIs and drush scripts to process production environments; I implemented this code in a development environment. Anyway..

I created a new content type "band", and added a CCK text/select field (genre) with an allowed values list:

Allowed value list before

I used Devel's auto-generate content functionality to create 20 nodes (/admin/generate/content), and then added a simple view to show the node's title and genre:

view of nodes before

Now that I had some working test data, I added some code to a custom module to modify the CCK field property settings page, and add a submit handler update the nodes:

<?php
// define module constants
define('CCK_FIELD_PROCESS', 'field_genre');
define('CCK_TYPE_PROCESS', 'band');

/**
* Implements hook_form_alter()
*/
function helper_form_alter(&$form, &$form_state, $form_id) {
 
 
// check for CCK form, CCK type, and CCK field (see above constants)
 
if ($form_id == 'content_field_edit_form' && $form['type_name']['#value'] == CCK_TYPE_PROCESS && $form['field_name']['#value'] == CCK_FIELD_PROCESS) {
   
   
// get original allowed values
   
$original_allowed_values_string = $form['field']['allowed_values_fieldset']['allowed_values']['#default_value'];
   
   
// explode values, get array of values   
   
$exploded = explode("\n", $original_allowed_values_string);
   
$original_allowed_values_array = array();
    foreach (
$exploded as $data) {

     
// explode data on "|"
     
list($key, $value) = $exploded2 = explode('|', $data);     
     
$original_allowed_values_array[$key] = $value;
     
    }
   
   
// ensure data exists
   
if (empty($original_allowed_values_array)) {
      return;
    }

   
// store original values in form state storage
   
$form_state['storage']['original_allowed_values'] = $original_allowed_values_array;
   
   
// store other information in form state
   
$form_state['storage']['cck_type'] = CCK_TYPE_PROCESS;
   
$form_state['storage']['cck_field'] = CCK_FIELD_PROCESS;

   
// add submit handler
   
$form['#submit'][] = '_helper_form_alter_content_field_edit_form_submit';
       
  }

}

/**
* Implements custom form submit handler
*/
function _helper_form_alter_content_field_edit_form_submit($form, &$form_state) {

 
// get new allowed values list
 
$new_allowed_values_string = $form_state['values']['allowed_values'];
 
 
// ensure data exists
 
if (empty($new_allowed_values_string)) {
    return;
  }

 
// explode values, get array of values   
 
$exploded = explode("\n", $new_allowed_values_string);
 
$new_allowed_values_array = array();
  foreach (
$exploded as $data) {

   
// explode data on "|"
   
list($key, $value) = $exploded2 = explode('|', $data);     
   
$new_allowed_values_array[$key] = $value;
   
  } 

 
// ensure data exists
 
if (empty($new_allowed_values_array)) {
    return;
  }
 
 
// NOTE: the next few lines gets the key values from the allowed values
  // and checks to see which are different.
  // This code assumes any change to the keys (including changing the order) will update the nodes.
  // Update to your heart's content
 
$new_keys = array_keys($new_allowed_values_array);
 
$old_keys = array_keys($form_state['storage']['original_allowed_values']);
 
 
$key_diffs = array();
  foreach (
$old_keys as $key => $value) {
   
    if (
$new_keys[$key] != $value) {
     
$key_diffs[$value] = $new_keys[$key];
    }
   
  }
 
 
// ensure data has changed
 
if (empty($key_diffs)) {
    unset(
$form_state['storage']);
    return;
  }
 
 
// get data from form storage
 
$cck_field = $form_state['storage']['cck_field'];
 
$cck_type = $form_state['storage']['cck_type'];
 
 
// loop through key diffs
 
foreach ($key_diffs as $old => $new) {
 
   
// get a list of node ids that need to be updated
    // NOTE: this sql assumes the field is NOT shared
   
$sql = "
      select n.nid
      from {node} n
      join {content_type_%s} b on b.nid = n.nid and b.vid = n.vid
      where n.type = '%s' and b.%s_value = '%s'
    "
;
   
$resource = db_query($sql, $cck_type, $cck_type, $cck_field, $old);
   
$node_ids = array();
    while (
$row = db_fetch_object($resource)) {
     
$node_ids[] = $row->nid;
    }
   
   
// ensure results exist
   
if (empty($node_ids)) {
      continue;
    }
   
   
// loop through node ids
    // NOTE: got a lot of nodes? you might need a batch API instead
   
foreach ($node_ids as $nid) {
     
     
// load node
     
$node = node_load($nid, NULL, TRUE);
     
     
// ensure node loaded
     
if (!is_object($node)) {
        continue;
      }
     
     
// update node properties
     
$node->{$cck_field}[0]['value'] = $new;
     
$node->revision = TRUE;
     
node_save($node);
     
     
// remove cck cache for node
     
db_query("DELETE FROM {cache_content} WHERE cid LIKE 'content:%d%'", $node->nid);
     
    }
 
  }
 
 
// remove form storage
 
unset($form_state['storage']);

}
?>

To test this code, I went back to the CCK field settings page (example: /admin/content/node-type/band/fields/field_genre), updated the allowed value list, and saved.

updated allowed value list

The form submit handler is triggered and the nodes are updated. Refreshing my view shows the updated genre fields on my test nodes:

Updated node list

Eric.London's picture

For my blog, I thought it would be great if I was notified when a comment was submitted. Like most of my articles, I try to explain how to accomplish something by implementing your own module hooks. In this article, I'll explain how I modified the comment form using a form_alter hook, and added an additional submit handler to send an email using drupal_mail.

First, I defined a form_alter hook to modify the comment form submit handlers

<?php
function MYMODULE_form_alter(&$form, $form_state, $form_id) {
 
// test for comment form
 
if ($form_id == 'comment_form') {
   
// add an additional submit handler
   
$form['#submit'][] = '_MYMODULE_comment_form_submit';
  }
}
?>

Next, I created the submit handler function

<?php
function _MYMODULE_comment_form_submit($form, &$form_state) {

 
// create an array of parameters
 
$params = array(
   
// NOTE: I tried using l() here,
    // but links don't work very well in text emails
       
    // this link will send me back to the node with an anchor to the comment
   
'commentLink' => 'http://' . $_SERVER['HTTP_HOST'] . base_path() .
     
$form_state['redirect'][0] . '#' . $form_state['redirect'][2]
  );
   
 
// call mail function and send email
 
drupal_mail('MYMODULE', 'comment_submitted',
   
variable_get('site_mail','MYDEFAULTEMAIL'), language_default(), $params);

}
?>

Lastly, I defined the mail hook

<?php
function MYMODULE_mail($key, &$message, $params) {
 
$language = $message['language'];
  switch(
$key) {
    case
'comment_submitted':
           
     
// create an array of variables from the parameters argument
     
$variables = array(
       
'!commentLink' => $params['commentLink'],
      );
           
     
// define subject and body
     
$message['subject'] = t("A comment has been submitted");
     
$message['body'] = t("!commentLink", $variables, $language->language);
           
      break;
  }
}
?>

Now when a comment is posted, my submit handler is called which notifies me of the new comment.

Eric.London's picture

Unfortunately, the stock Drupal contact module does not satisfy everyone's functional requirements. I decided to weigh alternative solutions, but since all I wanted to do was add an additional field to the form and email, I decided to add a few lines of module code instead. This solution consists of adding a form_alter hook to modify the contact form and submit handler, and an additional submit handler function:

<?php
function MYMODULE_form_alter(&$form, $form_state, $form_id) {
 
// check for contact form
 
if ($form_id == 'contact_mail_page') {
       
   
// prefix another submit handler
   
array_unshift($form['#submit'], '_MYMODULE_mail_page_submit');
       
   
// NOTE: since I want to insert the form element at a certain point,
    // I have to create a new $form object
   
$newForm = array();
       
   
// define insertion point
   
$ip = 'message';
       
   
// lop through $form object and duplicate the values
   
foreach ($form as $k => $v) {
           
     
// check for insertion point and add new form element
     
if ($k == $ip) {
       
// add phone number
       
$newForm['phone'] = array(
         
'#type' => 'textfield',
         
'#required' => true,
         
'#title' => 'Phone',
        );
      }
           
     
$newForm[$k] = $v;
    }
       
   
// replace $form object with new form
   
$form = $newForm;

  }
}

function
_MYMODULE_mail_page_submit($form, &$form_state) {   

 
// Since message is required by default,
  // I can concatenate the new form field onto the message variable
 
if (!empty($form_state['values']['phone'])) {
   
$form_state['values']['message'] .= "\n\nPhone: " . $form_state['values']['phone'];
  }
   
}
?>

Now, if I browse to the contact form, there is a new field (phone number in this example) inserted right above the message textarea. When I submit the form, the email sent contains the phone number as well.

This code snippet will should you how to add additional functionality to a system form when it's submitted. You can do this in Drupal 5.x using a form_alter hook to modify the submit handlers on the form object. In my example, I'll show you how to add your own function that will executed when the contact form is submitted.

<?php
function MYMODULE_form_alter($form_id, &$form) {
 
// check for the form ID you'd like to alter
 
if ($form_id == 'contact_mail_page') {
   
// modify the "#submit" form property by prepending another submit handler array
   
$form['#submit'] = array_merge(
      array(
'_MYMODULE_contant_mail_page_submit' => array()),
     
$form['#submit']
    );
  }
}
?>

Now, when the contact form is submitted, you're submit handler will be processed as well. You'll need to define a function with the same name.

<?php
function _MYMODULE_contant_mail_page_submit($form_id, $form_values) {
 
// do whatever you'd like here and it will be executed when the contact form is submitted
}
?>

Syndicate content