<?php

/**
 * @file
 * Install, update and uninstall functions for the field_collection module.
 */

/**
 * Implements hook_schema().
 */
function field_collection_schema() {

  $schema['field_collection_item'] = array(
    'description' => '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();
}
