Drupal 6: Updating nodes when changing CCK field allowed value list using a submit handler
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:
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:
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.
The form submit handler is triggered and the nodes are updated. Refreshing my view shows the updated genre fields on my test nodes: