'Stores information about field collection items.', 'fields' => array( 'item_id' => array( 'type' => 'serial', 'not null' => TRUE, 'description' => 'Primary Key: Unique field collection item ID.', ), 'revision_id' => array( 'type' => 'int', 'not null' => TRUE, 'description' => 'Default revision ID.', ), 'field_name' => array( 'description' => 'The name of the field on the host entity embedding this entity.', 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, ), 'archived' => array( 'description' => 'Boolean indicating whether the field collection item is archived.', 'type' => 'int', 'not null' => TRUE, 'default' => 0, ), ), 'primary key' => array('item_id'), ); $schema['field_collection_item_revision'] = array( 'description' => 'Stores revision information about field collection items.', 'fields' => array( 'revision_id' => array( 'type' => 'serial', 'not null' => TRUE, 'description' => 'Primary Key: Unique revision ID.', ), 'item_id' => array( 'type' => 'int', 'not null' => TRUE, 'description' => 'Field collection item ID.', ), ), 'primary key' => array('revision_id'), 'indexes' => array( 'item_id' => array('item_id'), ), 'foreign keys' => array( 'versioned_field_collection_item' => array( 'table' => 'field_collection_item', 'columns' => array('item_id' => 'item_id'), ), ), ); if (module_exists('entitycache')) { $schema['cache_entity_field_collection_item'] = drupal_get_schema_unprocessed('system', 'cache'); $schema['cache_entity_field_collection_item']['description'] = 'Cache table used to store field_collection_item entity records.'; } return $schema; } /** * Implements hook_field_schema(). */ function field_collection_field_schema($field) { $columns = array( 'value' => array( 'type' => 'int', 'not null' => FALSE, 'description' => 'The field collection item id.', ), 'revision_id' => array( 'type' => 'int', 'not null' => FALSE, 'description' => 'The field collection item revision id.', ), ); return array( 'columns' => $columns, 'indexes' => array( 'value' => array('value'), 'revision_id' => array('revision_id'), ), ); } /** * Update the administer field collection permission machine name. */ function field_collection_update_7000() { db_update('role_permission') ->fields(array('permission' => 'administer field collections')) ->condition('permission', 'administer field-collections') ->execute(); } /** * Add revision support. */ function field_collection_update_7001() { // Add revision_id column to field_collection_item table. $revision_id_spec = array( 'type' => 'int', 'not null' => TRUE, 'description' => 'Default revision ID.', // Set default to 0 temporarily. 'initial' => 0, ); // Field may already exist due to bug in 7.x-1.0-beta5. if (!db_field_exists('field_collection_item', 'revision_id')) { db_add_field('field_collection_item', 'revision_id', $revision_id_spec); } // Initialize the revision_id to be the same as the item_id. db_update('field_collection_item') ->expression('revision_id', 'item_id') ->execute(); // Add the archived column. $archived_spec = array( 'description' => 'Boolean indicating whether the field collection item is archived.', 'type' => 'int', 'not null' => TRUE, 'default' => 0, ); // Field may already exist due to bug in 7.x-1.0-beta5. if (!db_field_exists('field_collection_item', 'archived')) { db_add_field('field_collection_item', 'archived', $archived_spec); } // Create the new table. It is important to explicitly define the schema here // rather than use the hook_schema definition: http://drupal.org/node/150220. $schema['field_collection_item_revision'] = array( 'description' => 'Stores revision information about field collection items.', 'fields' => array( 'revision_id' => array( 'type' => 'serial', 'not null' => TRUE, 'description' => 'Primary Key: Unique revision ID.', ), 'item_id' => array( 'type' => 'int', 'not null' => TRUE, 'description' => 'Field collection item ID.', ), ), 'primary key' => array('revision_id'), 'indexes' => array( 'item_id' => array('item_id'), ), 'foreign keys' => array( 'versioned_field_collection_item' => array( 'table' => 'field_collection_item', 'columns' => array('item_id' => 'item_id'), ), ), ); // Table may already exist due to bug in 7.x-1.0-beta5. if (db_table_exists('field_collection_item_revision')) { db_drop_table('field_collection_item_revision'); } db_create_table('field_collection_item_revision', $schema['field_collection_item_revision']); // Fill the new table with the correct data. $items = db_select('field_collection_item', 'fci') ->fields('fci') ->execute(); foreach ($items as $item) { // Update field_collection_item_revision table. db_insert('field_collection_item_revision') ->fields(array( 'revision_id' => $item->item_id, 'item_id' => $item->item_id, )) ->execute(); } // Update the field_collection_field_schema columns for all tables. // Add a revision_id column. $revision_id_spec['description'] = 'The field collection item revision id.'; // Because $value_column below can be null, so must $revision_id_column. $revision_id_spec['not null'] = FALSE; foreach (field_read_fields(array('type' => 'field_collection')) as $field_name => $field) { $table_prefixes = array('field_data', 'field_revision'); foreach ($table_prefixes as $table_prefix) { $table = sprintf('%s_%s', $table_prefix, $field_name); $value_column = sprintf('%s_value', $field_name); $revision_id_column = sprintf('%s_revision_id', $field_name); // Field may already exist due to bug in 7.x-1.0-beta5. if (!db_field_exists($table, $revision_id_column)) { db_add_field($table, $revision_id_column, $revision_id_spec); } else { db_change_field($table, $revision_id_column, $revision_id_column, $revision_id_spec); } // Initialize the revision_id to be the same as the item_id. db_update($table) ->expression($revision_id_column, $value_column) ->execute(); } } // Need to get the system up-to-date so drupal_schema_fields_sql() will work. $schema = drupal_get_schema('field_collection_item_revision', TRUE); } /** * Remove orphaned field collection item entities. */ function field_collection_update_7002() { // Loop over all fields and delete any orphaned field collection items. foreach (field_read_fields(array('type' => 'field_collection')) as $field_name => $field) { $select = db_select('field_collection_item', 'fci') ->fields('fci', array('item_id')) ->condition('field_name', $field_name) ->condition('archived', 0); $select->leftJoin('field_data_' . $field_name, 'field', "field.{$field_name}_value = fci.item_id "); $select->isNull('field.entity_id'); $ids = $select->execute()->fetchCol(); entity_delete_multiple('field_collection_item', $ids); drupal_set_message(t('Deleted @count orphaned field collection items.', array('@count' => count($ids)))); } } /** * Update field_collection_field_schema columns for all tables. */ function field_collection_update_7003() { // Revision_id column. $revision_id_spec = array( 'type' => 'int', 'not null' => FALSE, 'description' => 'The field collection item revision id.', 'initial' => 0, ); // Update the field_collection_field_schema columns for all tables, // in case the buggy beta5 version of field_collection_update_7001() // completed without complaint. foreach (field_read_fields(array('type' => 'field_collection')) as $field_name => $field) { $table_prefixes = array('field_data', 'field_revision'); foreach ($table_prefixes as $table_prefix) { $table = sprintf('%s_%s', $table_prefix, $field_name); $value_column = sprintf('%s_value', $field_name); $revision_id_column = sprintf('%s_revision_id', $field_name); db_change_field($table, $revision_id_column, $revision_id_column, $revision_id_spec); } } // Need to get the system up-to-date so drupal_schema_fields_sql() will work. $schema = drupal_get_schema('field_collection_item_revision', TRUE); } /** * Add index on {$field_collection_field}_revision_id column for all tables. */ function field_collection_update_7004() { // Update the field_collection_field_schema columns for all tables. foreach (field_read_fields(array('type' => 'field_collection')) as $field_name => $field) { $table_prefixes = array('field_data', 'field_revision'); foreach ($table_prefixes as $table_prefix) { $table = sprintf('%s_%s', $table_prefix, $field_name); $revision_id_column = sprintf('%s_revision_id', $field_name); // Add index on revision_id column. if (!db_index_exists($table, $revision_id_column)) { db_add_index($table, $revision_id_column, array($revision_id_column)); } } } } /** * Force the creation of the table cache_entity_field_collection_item. * * entity_update_7003 will attempt to install entitycache tables for existing * modules, but it uses module_list() to get the list of available modules, * which, when called from a database update, may not return field_collection * since drupal is bootstrapped at a lower level. */ function field_collection_update_7005() { if (module_exists('entitycache')) { $entity_type = 'field_collection_item'; $table = 'cache_entity_' . $entity_type; if (!db_table_exists($table)) { $schema = drupal_get_schema_unprocessed('system', 'cache'); $schema['description'] = 'Cache table used to store' . $entity_type . ' entity records.'; db_create_table($table, $schema); } } } /** * Ensures revision_id indexes are present at field_config table. */ function field_collection_update_7006() { $result = db_query("SELECT id, field_name, data FROM {field_config} WHERE type = 'field_collection'"); foreach ($result as $field_config) { $data = unserialize($field_config->data); // Skip this record if the revision_id index is already present. if (isset($data['indexes']['revision_id'])) { continue; } // Otherwise, add the revision_id index and update the record. $data['indexes']['revision_id'] = array('revision_id'); $data = serialize($data); $num_updated = db_update('field_config') ->fields(array('data' => $data)) ->condition('id', $field_config->id) ->execute(); // If for some reason the update failed, throw an exception. if ($num_updated != 1) { $t_args['@field'] = $field_config->field_name; throw new DrupalUpdateException(t('An error was detected when attempting to update field configuration for field @field.', $t_args)); } } } /** * Add index on {$field_collection_field}_value column for all tables. */ function field_collection_update_7007() { foreach (field_read_fields(array('type' => 'field_collection')) as $field_name => $field) { if (!isset($field['indexes']['value'])) { // Add index on the value column and update the field. $field['indexes']['value'] = array('value'); field_update_field($field); } $table_prefixes = array('field_data', 'field_revision'); foreach ($table_prefixes as $table_prefix) { $table = "{$table_prefix}_{$field_name}"; $value_column = "{$field_name}_value"; if (!db_index_exists($table, $value_column)) { // Add index on the value column. db_add_index($table, $value_column, array($value_column)); } } } } /** * Update fields in field collections already set to use Entity Translation. */ function field_collection_update_7008() { // Include FieldCollectionItemEntity class. module_load_include('inc', 'field_collection', 'field_collection.entity'); $results = array(); foreach (field_info_fields() as $f_name => $field) { if ($field['translatable'] == 1 && isset($field['bundles']['field_collection_item'])) { $query = new EntityFieldQuery(); $query->entityCondition('entity_type', 'field_collection_item') ->fieldLanguageCondition($f_name, LANGUAGE_NONE); $query_result = $query->execute(); if (isset($query_result['field_collection_item'])) { $results += $query_result['field_collection_item']; } } } if (count($results)) { $orphans = array(); $ids = array_keys($results); $field_collection_items = entity_load('field_collection_item', $ids); foreach ($field_collection_items as $item) { /** @var FieldCollectionItemEntity $item */ if ($item->hostEntity()) { $item->copyTranslations(LANGUAGE_NONE); $item->save(TRUE); } else { $orphans[] = $item->identifier(); } } if ($orphans) { $count = count($orphans); entity_delete_multiple('field_collection_item', $orphans); drupal_set_message("Deleted $count orphaned field collection items."); } } } /** * Update revisions created before revision support was implemented. */ function field_collection_update_7009(&$sandbox) { $items_per_pass = 10; $field_collection_fields = field_read_fields(array('type' => 'field_collection')); // If you don't have any field_collecton fields then skip this update. if (empty($field_collection_fields)) { return; } if (!isset($sandbox['current_field'])) { $sandbox['field_collection_fields'] = array_keys($field_collection_fields); $sandbox['current_field'] = 0; $sandbox['current_field_collection_item'] = 0; $sandbox['field_name'] = $sandbox['field_collection_fields'][$sandbox['current_field']]; $sandbox['inner_fields'] = field_read_instances(array('entity_type' => 'field_collection_item', 'bundle' => $sandbox['field_name'])); } // Find field collection items with duplicate revision ids. $query = db_select("field_revision_{$sandbox['field_name']}", 't') ->fields('t', array("{$sandbox['field_name']}_revision_id")) ->range($sandbox['current_field_collection_item'], $items_per_pass) ->groupBy("t.{$sandbox['field_name']}_revision_id"); $query->having("COUNT(t.{$sandbox['field_name']}_revision_id) >= 2"); $vids = $query->execute()->fetchCol(); // Each revision ID that occurs more than once. foreach ($vids as $field_collection_duplicated_revision_id) { // Find every instance of this revision ID. $copies = db_select("field_revision_{$sandbox['field_name']}", 't') ->fields('t') ->condition("{$sandbox['field_name']}_revision_id", $field_collection_duplicated_revision_id) ->execute() ->fetchAllAssoc('revision_id'); $first_copy = reset($copies); $field_collection_item_id = $first_copy->{"{$sandbox['field_name']}_value"}; // Find the currently used revision of this field collection item. $field_collection_item_current_revision = db_select('field_collection_item', 'fci') ->fields('fci', array('revision_id')) ->condition('item_id', $field_collection_item_id) ->execute() ->fetchField(); // Find new revisions of this field collection item that were made after // revisions were enabled in update_7003. $modern_revisions = db_select("field_revision_{$sandbox['field_name']}", 't') ->fields('t') ->condition("{$sandbox['field_name']}_revision_id", $field_collection_duplicated_revision_id, '<>') ->condition("{$sandbox['field_name']}_value", $field_collection_item_id) ->orderBy('revision_id') ->execute() ->fetchAllAssoc('revision_id'); // Intentionally skip the first instance as it doesn't need to be modified. while (FALSE !== ($row_to_replace = next($copies))) { $new_revision_id = _field_collection_update_7009_new_revision($field_collection_item_id, $row_to_replace, $sandbox['inner_fields'], $sandbox['field_name']); // Create copies of inner fields with new revision number. foreach ($sandbox['inner_fields'] as $field) { // Get the data to copy. $data_rows = db_select("field_revision_{$field['field_name']}", 't') ->fields('t') ->condition('entity_type', 'field_collection_item') ->condition('revision_id', $field_collection_duplicated_revision_id) ->execute(); // Add new copy of data with new revision id. while ($each_row = $data_rows->fetchAssoc()) { $each_row['revision_id'] = $new_revision_id; db_insert("field_revision_{$field['field_name']}") ->fields(array_keys($each_row), array_values($each_row)) ->execute(); } } // Update the host's field data with new revision number. db_update("field_revision_{$sandbox['field_name']}") ->fields(array("{$sandbox['field_name']}_revision_id" => $new_revision_id)) ->condition('entity_type', $row_to_replace->entity_type) ->condition('revision_id', $row_to_replace->revision_id) ->condition('delta', $row_to_replace->delta) ->condition('language', $row_to_replace->language) ->execute(); if ($field_collection_item_current_revision == $field_collection_duplicated_revision_id) { _field_collection_update_7009_update_data($new_revision_id, $field_collection_duplicated_revision_id); // Update the current field collection item data in its host. db_update("field_data_{$sandbox['field_name']}") ->fields(array('revision_id' => $new_revision_id)) ->condition('revision_id', $field_collection_duplicated_revision_id) ->condition('entity_type', $row_to_replace->entity_type) ->execute(); } } foreach ($modern_revisions as $each_modern_revision) { $new_revision_id = _field_collection_update_7009_new_revision($field_collection_item_id, $each_modern_revision, $sandbox['inner_fields'], $sandbox['field_name']); // Update inner fields with new revision number. foreach ($sandbox['inner_fields'] as $field) { db_update("field_revision_{$field['field_name']}") ->fields(array('revision_id' => $new_revision_id)) ->condition('revision_id', $each_modern_revision->{$sandbox['field_name'] . '_revision_id'}) ->condition('entity_type', 'field_collection_item') ->execute(); } // Update the host's field data with new revision number. db_update("field_revision_{$sandbox['field_name']}") ->fields(array("{$sandbox['field_name']}_revision_id" => $new_revision_id)) ->condition('entity_type', $each_modern_revision->entity_type) ->condition('revision_id', $each_modern_revision->revision_id) ->condition('delta', $each_modern_revision->delta) ->condition('language', $each_modern_revision->language) ->execute(); if ($field_collection_item_current_revision == $each_modern_revision->{"{$sandbox['field_name']}_revision_id"}) { _field_collection_update_7009_update_data($new_revision_id, $field_collection_item_current_revision); // Update the current field collection item data in its host. db_update("field_data_{$sandbox['field_name']}") ->fields(array('revision_id' => $new_revision_id)) ->condition('revision_id', $field_collection_item_current_revision) ->condition('entity_type', $each_modern_revision->entity_type) ->execute(); } // Remove old copy of revision. db_delete('field_collection_item_revision') ->condition('revision_id', $each_modern_revision->revision_id) ->execute(); } $sandbox['current_field_collection_item']++; } $sandbox['#finished'] = 0; if (count($vids) < $items_per_pass) { $sandbox['current_field']++; if ($sandbox['current_field'] == count($sandbox['field_collection_fields'])) { $sandbox['#finished'] = 1; return; } $sandbox['current_field_collection_item'] = 0; $sandbox['field_name'] = $sandbox['field_collection_fields'][$sandbox['current_field']]; $sandbox['inner_fields'] = field_read_instances(array('entity_type' => 'field_collection_item', 'bundle' => $sandbox['field_name'])); } } function _field_collection_update_7009_new_revision($field_collection_item_id, $row_to_replace, $inner_fields, $field_name) { // Add to field_collection_item_revision table. $new_revision_id = db_insert('field_collection_item_revision') ->fields(array('item_id'), array($field_collection_item_id)) ->execute(); return $new_revision_id; } function _field_collection_update_7009_update_data($new_revision, $old_revision) { // Update the current field collection item. db_update('field_collection_item') ->fields(array('revision_id' => $new_revision)) ->condition('revision_id', $old_revision) ->execute(); }