You are here

function system_update_8400 in Drupal 8

Move revision metadata fields to the revision table.

File

core/modules/system/system.install, line 2255
Install, update and uninstall functions for the system module.

Code

function system_update_8400(&$sandbox) {

  // Due to the fields from RevisionLogEntityTrait not being explicitly
  // mentioned in the storage they might have been installed wrongly in the base
  // table for revisionable untranslatable entities and in the data and revision
  // data tables for revisionable and translatable entities.
  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
  $database = \Drupal::database();
  $database_schema = $database
    ->schema();
  if (!isset($sandbox['current'])) {

    // This must be the first run. Initialize the sandbox.
    $sandbox['current'] = 0;
    $definitions = array_filter(\Drupal::entityTypeManager()
      ->getDefinitions(), function (EntityTypeInterface $entity_type) use ($entity_definition_update_manager) {
      if ($entity_type = $entity_definition_update_manager
        ->getEntityType($entity_type
        ->id())) {
        return is_subclass_of($entity_type
          ->getClass(), FieldableEntityInterface::class) && $entity_type instanceof ContentEntityTypeInterface && $entity_type
          ->isRevisionable();
      }
      return FALSE;
    });
    $sandbox['entity_type_ids'] = array_keys($definitions);
    $sandbox['max'] = count($sandbox['entity_type_ids']);
  }
  $current_entity_type_key = $sandbox['current'];
  for ($i = $current_entity_type_key; $i < $current_entity_type_key + 1 && $i < $sandbox['max']; $i++) {
    $entity_type_id = $sandbox['entity_type_ids'][$i];

    /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
    $entity_type = $entity_definition_update_manager
      ->getEntityType($entity_type_id);
    $base_fields = \Drupal::service('entity_field.manager')
      ->getBaseFieldDefinitions($entity_type_id);
    $revision_metadata_fields = $entity_type
      ->getRevisionMetadataKeys();
    $fields_to_update = array_intersect_key($base_fields, array_flip($revision_metadata_fields));
    if (!empty($fields_to_update)) {

      // Initialize the entity table names.
      // @see \Drupal\Core\Entity\Sql\SqlContentEntityStorage::initTableLayout()
      $base_table = $entity_type
        ->getBaseTable() ?: $entity_type_id;
      $data_table = $entity_type
        ->getDataTable() ?: $entity_type_id . '_field_data';
      $revision_table = $entity_type
        ->getRevisionTable() ?: $entity_type_id . '_revision';
      $revision_data_table = $entity_type
        ->getRevisionDataTable() ?: $entity_type_id . '_field_revision';
      $revision_field = $entity_type
        ->getKey('revision');

      // No data needs to be migrated if the entity type is not translatable.
      if ($entity_type
        ->isTranslatable()) {
        if (!isset($sandbox[$entity_type_id])) {

          // This must be the first run for this entity type. Initialize the
          // sub-sandbox for it.
          // Calculate the number of revisions to process.
          $count = \Drupal::entityQuery($entity_type_id)
            ->allRevisions()
            ->count()
            ->accessCheck(FALSE)
            ->execute();
          $sandbox[$entity_type_id]['current'] = 0;
          $sandbox[$entity_type_id]['max'] = $count;
        }

        // Define the step size.
        $steps = Settings::get('entity_update_batch_size', 50);

        // Collect the revision IDs to process.
        $revisions = \Drupal::entityQuery($entity_type_id)
          ->allRevisions()
          ->range($sandbox[$entity_type_id]['current'], $sandbox[$entity_type_id]['current'] + $steps)
          ->sort($revision_field, 'ASC')
          ->accessCheck(FALSE)
          ->execute();
        $revisions = array_keys($revisions);
        foreach ($fields_to_update as $revision_metadata_field_name => $definition) {

          // If the revision metadata field is present in the data and the
          // revision data table, install its definition again with the updated
          // storage code in order for the field to be installed in the
          // revision table. Afterwards, copy over the field values and remove
          // the field from the data and the revision data tables.
          if ($database_schema
            ->fieldExists($data_table, $revision_metadata_field_name) && $database_schema
            ->fieldExists($revision_data_table, $revision_metadata_field_name)) {

            // Install the field in the revision table.
            if (!isset($sandbox[$entity_type_id]['storage_definition_installed'][$revision_metadata_field_name])) {
              $entity_definition_update_manager
                ->installFieldStorageDefinition($revision_metadata_field_name, $entity_type_id, $entity_type
                ->getProvider(), $definition);
              $sandbox[$entity_type_id]['storage_definition_installed'][$revision_metadata_field_name] = TRUE;
            }

            // Apply the field value from the revision data table to the
            // revision table.
            foreach ($revisions as $rev_id) {
              $field_value = $database
                ->select($revision_data_table, 't')
                ->fields('t', [
                $revision_metadata_field_name,
              ])
                ->condition($revision_field, $rev_id)
                ->execute()
                ->fetchField();
              $database
                ->update($revision_table)
                ->condition($revision_field, $rev_id)
                ->fields([
                $revision_metadata_field_name => $field_value,
              ])
                ->execute();
            }
          }
        }
        $sandbox[$entity_type_id]['current'] += count($revisions);
        $sandbox[$entity_type_id]['finished'] = $sandbox[$entity_type_id]['current'] == $sandbox[$entity_type_id]['max'] || empty($revisions);
        if ($sandbox[$entity_type_id]['finished']) {
          foreach ($fields_to_update as $revision_metadata_field_name => $definition) {

            // Drop the field from the data and revision data tables.
            $database_schema
              ->dropField($data_table, $revision_metadata_field_name);
            $database_schema
              ->dropField($revision_data_table, $revision_metadata_field_name);
          }
          $sandbox['current']++;
        }
      }
      else {
        foreach ($fields_to_update as $revision_metadata_field_name => $definition) {
          if ($database_schema
            ->fieldExists($base_table, $revision_metadata_field_name)) {

            // Install the field in the revision table.
            $entity_definition_update_manager
              ->installFieldStorageDefinition($revision_metadata_field_name, $entity_type_id, $entity_type
              ->getProvider(), $definition);

            // Drop the field from the base table.
            $database_schema
              ->dropField($base_table, $revision_metadata_field_name);
          }
        }
        $sandbox['current']++;
      }
    }
    else {
      $sandbox['current']++;
    }
  }
  $sandbox['#finished'] = $sandbox['current'] == $sandbox['max'];
}