Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Drupal script to fix field collection items associated with multiple entities due to Content Translation/Node Clone bug.


Drupal script to fix field collection items associated with multiple entities due to Content Translation/Node Clone bug.

**NOTE: This update hook gives every field collection item found to be associated with more than one node a new item_id. I haven't experienced any problems with doing this, but if you are doing anything special in your use case with the item_ids or revision_ids of field collection items, you should test thoroughly after running this update.

You should test thoroughly anyway, really.


How to use

Add this as an update hook to a custom module.


Sponsored by Project Ricochet

* Fix up duplicated field collection IDs.
function yourmodule_workflow_update_7001() {
// NOTE: You can change the update number above if the install file you are adding this to already has other updates.
// This update probably assumes field_collection is enabled. You should consider making your module depend on it.
// You also need an updated version of field collection because field_collection_update_7002() is called manually at the end of this one.
$already_done = array();
$changed_deltas = array();
foreach (field_read_fields(array('type' => 'field_collection')) as $field_name => $field) {
drupal_set_message("Now checking $field_name...");
$changed_deltas[$field_name] = array();
$already_done[$field_name] = array();
// Get the field collection IDs that appear in more than one language
/* select f.field_collection_name_value AS field_collection_name_value, f.entity_id AS entity_id,
COUNT(f.entity_id) AS c
field_data_field_collection_name f
GROUP BY f.field_collection_name_value, delta
HAVING COUNT(entity_id) > 1 ORDER BY count(entity_id) desc; */
$query1 = db_select("field_data_{$field_name}", 'f')
->fields('f', array("{$field_name}_value"))
->condition('f.entity_type', 'node')
$count_alias = $query1->addExpression("COUNT(f.entity_id)", 'c');
$query1 = $query1->havingCondition($count_alias, "1", '>');
$the_results = $query1->execute();
foreach ($the_results as $data) {
// Get the associated entity IDs
// "select distinct entity_id from field_data_:field_name JOIN node on node.nid = entity_id WHERE :field_name = :field_name_value AND node.language != 'en'", array(':field_name' => $field_name, ':field_name_value' => $data->$field_name_value)
$query2 = db_select("field_data_{$field_name}", "f")
->fields('f', array('entity_id'))
->condition("f.{$field_name}_value", $data->{"{$field_name}_value"});
// ->condition("n.language", "en", "!="); // Removed - now it'll over-correct, but that's OK. It was under-correcting before
$query2->join("node", "n", "n.nid = f.entity_id");
$entity_ids = $query2->execute();
foreach ($entity_ids as $entity_id) {
if (!isset($already_done[$field_name][$entity_id->entity_id])) {
drupal_set_message("Re-initializing field collection on node {$entity_id->entity_id}.");
$vid_query = db_query("select vid from {node} where nid = :entity_id ORDER BY vid ASC", array(":entity_id" => $entity_id->entity_id));
foreach($vid_query as $vid_data) {
$vid = $vid_data->vid;
$changed_deltas[$field_name][$vid] = array();
$loaded_revision = node_load(NULL, $vid, TRUE);
// Get the field collection item IDs
$vid_field_collections = field_get_items('node', $loaded_revision, $field_name);
foreach ($vid_field_collections as $delta => $vid_field_collection) {
// Now load the field_collection_item
$vid_fc = field_collection_item_revision_load($vid_field_collection['revision_id']);
if (!$vid_fc) {
// OK, try loading it by item ID then.
$vid_fc = field_collection_item_load($vid_field_collection['value']);
if (!$vid_fc) {
throw new Exception("Field: {$field_name} -
Node: {$entity_id->entity_id} -
Loading the field collection item with item ID {$vid_field_collection['value']} and revision ID {$vid_field_collection['revision_id']} failed for some reason! We couldn't load it by field collection ID either. -
Investigate why before continuing.");
drupal_set_message("Loading the database revision for {$field_name} (item ID: {$vid_field_collection['value']} on node {$entity_id->entity_id}/{$vid} failed, but we were able to load it by item ID. The revision ID that failed was {$vid_field_collection['revision_id']}, and the one we actually loaded was {$vid_fc->revision_id}.", 'warning');
// OK, we have it loaded now. Set the item ID to an empty string then re-save it. It should update itself with the host automatically.
$vid_fc->item_id = '';
drupal_set_message("Saved a new field collection for node {$entity_id->entity_id}, revision {$loaded_revision->vid}. It says its item ID is {$vid_fc->item_id} and its revision ID is {$vid_fc->revision_id}. The old item ID was {$vid_field_collection['value']}, and the old revision ID was {$vid_field_collection['revision_id']}.");
$changed_deltas[$field_name][$vid][$delta] = $vid_fc;
$already_done[$field_name][$entity_id->entity_id] = $entity_id->entity_id;
// Save everything. We couldn't do it earlier because it would prematurely delete the field collections and mess up our fixes.
if (count($changed_deltas) > 0) {
foreach ($changed_deltas as $field_name => $vid_array) {
drupal_set_message("Saving changes for {$field_name}...");
foreach($vid_array as $node_vid => $delta_array) {
// Avoid memory problems, hopefully?
$revision_to_save = node_load(NULL, $node_vid, TRUE);
// Update the revision with the IDs of the new field collection item
foreach ($delta_array as $new_delta => $new_fc) {
// drupal_set_message("Applying new field collection {$new_fc->item_id} to revision {$revision_to_save->vid}");
$field_info = field_info_field($field_name);
$lang = $field_info['translatable'] ? entity_language('node', $revision_to_save) : LANGUAGE_NONE;
$revision_to_save->{$field_name}[$lang][$new_delta]['value'] = $new_fc->item_id;
$revision_to_save->{$field_name}[$lang][$new_delta]['revision_id'] = $new_fc->revision_id;
drupal_set_message("Saved node revision {$revision_to_save->vid} with new field collection values.");
// Remove any orphaned records.
return "Fix up duplicated field collection IDs.";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment