Skip to content

Instantly share code, notes, and snippets.

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

Explanation

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.

TAKE A BACKUP.

How to use

Add this as an update hook to a custom module.

Credits

Sponsored by Project Ricochet

<?php
# THE FILE EXTENSION SHOULD BE .install, BUT GITHUB GISTS DON'T HIGHLIGHT SYNTAX UNLESS I ADD .php.
/**
* 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
FROM
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')
->groupBy("f.{$field_name}_value")
->groupBy('delta');
$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'))
->distinct()
->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 = '';
$vid_fc->save(TRUE);
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;
}
node_save($revision_to_save);
drupal_set_message("Saved node revision {$revision_to_save->vid} with new field collection values.");
}
}
}
// Remove any orphaned records.
field_collection_update_7002();
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
You can’t perform that action at this time.