media_acquiadam-8.x-1.46/src/Batch/MediaTypeProcessBatch.php

src/Batch/MediaTypeProcessBatch.php
<?php

declare(strict_types=1);

namespace Drupal\media_acquiadam\Batch;

use Drupal\Core\Config\Config;
use Drupal\Core\Database\Database;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannel;
use Drupal\acquia_dam\Entity\ManagedFileField;
use Drupal\acquia_dam\Entity\ManagedImageField;
use Drupal\acquia_dam\Entity\MediaSourceField;
use Drupal\media\MediaInterface;
use Drupal\media\MediaTypeInterface;

/**
 * Method to handle media types during the Acquia DAM migration process.
 *
 * @see \Drupal\media_acquiadam\Form\AcquiadamMigration
 */
class MediaTypeProcessBatch {

  /**
   * Processes one media type at a time per the received requirements.
   *
   * @param string $operation
   *   One of either 'update' or 'delete'.
   * @param string $media_type_id
   *   Machine name of the media type to work on.
   * @param array $config_data
   *   A package of settings to perform.
   * @param array $context
   *   The batch context.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public static function process(string $operation, string $media_type_id, array $config_data, &$context): void {
    $entity_type_manager = \Drupal::entityTypeManager();
    /** @var \Drupal\Core\Logger\LoggerChannel $logger */
    $logger = \Drupal::logger('media_acquiadam');
    /** @var \Drupal\media\MediaTypeInterface $media_type */
    $media_type = $entity_type_manager->getStorage('media_type')->load($media_type_id);

    // If the media type cannot be loaded, log an error and return.
    if (!$media_type) {
      $logger->error(t('Media type “@machine_name” cannot be loaded, thus was skipped during Acquia DAM migration.', [
        '@machine_name' => $media_type_id,
      ]));
      return;
    }

    // Perform the operation on the media type.
    switch ($operation) {
      case 'delete':
        // Delete the media type.
        self::deleteMediaType($media_type, $media_type_id, $logger);
        break;

      case 'update':
        // Update the media type.
        $operations_performed = self::updateMediaType($media_type, $config_data, $media_type_id, $logger);

        // Update the media_library view display for the media type.
        self::updateMediaLibraryViewMode($media_type_id, $logger);

        // Update the view displays for the media type.
        self::updateViewDisplays($media_type, $config_data, $media_type_id);

        // Leave traces in Watchdog.
        $logger->notice(t('The following operations has been performed on the @machine_name media type during the Acquia DAM migration: @list_of_operations.', [
          '@machine_name' => $media_type_id,
          '@list_of_operations' => implode(', ', $operations_performed),
        ]));
        break;
      case 'update_modern_media_label':
        // Update the media type label.
        $media_type->set('label', $config_data['media_type_label']);
        try {
          // Save the media type.
          $media_type->save();
        }
        catch (EntityStorageException $e) {
          $logger->error(t('An error happened while saving media type @machine_name. @exception_message', [
            '@machine_name' => $media_type_id,
            '@exception_message' => $e->getMessage(),
          ]));
        }
        break;
    }

  }

  /**
   * Delete media type.
   *
   * @param \Drupal\media\MediaTypeInterface $media_type
   *   The media type.
   * @param string $media_type_id
   *   The media type ID.
   * @param \Drupal\Core\Logger\LoggerChannel $logger
   *   The logger.
   */
  protected static function deleteMediaType(MediaTypeInterface $media_type, string $media_type_id, LoggerChannel $logger): void {
    try {
      // Delete the media type.
      $media_type->delete();
      $deleted_media_type = \Drupal::state()->get('media_acquiadam.delete_media_type', []);
      $message = "Media type " . $media_type->label() . "(" . $media_type_id . ") has been deleted during Acquia DAM migration. \n";
      $deleted_media_type[$media_type_id] = $message;
      // Store deleted media types in the states.
      \Drupal::state()->set('media_acquiadam.delete_media_type', $deleted_media_type);
      $logger->notice(t('@message', [
        '@message' => $message,
      ]));
    }
    catch (EntityStorageException $e) {
      $logger->error(t('An error happened while deleting media type @machine_name. @exception_message', [
        '@machine_name' => $media_type_id,
        '@exception_message' => $e->getMessage(),
      ]));
    }
  }

  /**
   * Update the media type with given configuration.
   *
   * @param \Drupal\media\MediaTypeInterface $media_type
   *   The media type.
   * @param array $config_data
   *   The config data.
   * @param string $media_type_id
   *   The media type ID.
   * @param \Drupal\Core\Logger\LoggerChannel $logger
   *   The logger.
   *
   * @return array
   *   The array of operations performed.
   */
  protected static function updateMediaType(MediaTypeInterface $media_type, array $config_data, string $media_type_id, LoggerChannel $logger): array {
    // Set the media type to syncing.
    $media_type->setSyncing(TRUE);
    $operations_performed = [];

    // Update the label of the media type if it has changed.
    if (isset($config_data['media_type_label'])) {
      $new_label = $config_data['media_type_label'];
      $operations_performed[] = "renaming from “{$media_type->label()}” to “{$new_label}”";
      $media_type->set('label', $new_label);
    }

    // Configure the media type to either download assets or embed them
    // based on the provided settings.
    if (isset($config_data['sync_method'])) {
      $operations_performed[] = "fetch method switched to “{$config_data['sync_method']}”";
      $source_configuration = $media_type->get('source_configuration');
      $source_configuration['download_assets'] = $config_data['sync_method'] === 'sync';
      $media_type->set('source_configuration', $source_configuration);
    }

    // Update the source plugin of the media type.
    if (isset($config_data['target_source_type'])) {
      $new_source_plugin = $config_data['target_source_type'];
      $operations_performed[] = "replacing its source plugin from “{$media_type->get('source')}” to “{$new_source_plugin}”";
      $media_type->set('source', $new_source_plugin);
    }

    try {
      // Update the media type dependencies.
      $media_type->calculateDependencies();

      // Save the media type.
      $media_type->save();
    }
    catch (EntityStorageException $e) {
      $logger->error(t('An error happened while saving media type @machine_name. @exception_message', [
        '@machine_name' => $media_type_id,
        '@exception_message' => $e->getMessage(),
      ]));
    }

    return $operations_performed;
  }

  /**
   * Update the media_library view display for the media type.
   *
   *  Ensures that the Media Library view display is set up correctly to
   *  prevent distorted media thumbnail displays after migration.
   *  If the display configuration is missing for any media type, this method
   *  will create it.
   *
   * @param string $media_type_id
   *   The media type ID.
   * @param \Drupal\Core\Logger\LoggerChannel $logger
   *   The logger.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected static function updateMediaLibraryViewMode(string $media_type_id, LoggerChannel $logger): void {
    // Load media library view display configuration.
    $view_display = EntityViewDisplay::load("media.{$media_type_id}.media_library");

    // Create media library view display configuration if not exist.
    if (!$view_display) {
      $view_display = EntityViewDisplay::create([
        'targetEntityType' => 'media',
        'bundle' => $media_type_id,
        'mode' => 'media_library',
        'status' => TRUE,
      ]);
    }

    // Remove all existing components from the display.
    foreach (array_keys($view_display->getComponents()) as $field_name) {
      $view_display->removeComponent($field_name);
    }

    // Update the media library view display with our changes.
    $view_display->setComponent('acquia_dam_asset_id', [
      'type' => 'acquia_dam_embed_code',
      'label' => 'hidden',
      'settings' => [
        'embed_style' => 'remotely_referenced_thumbnail_image',
        'thumbnail_width' => 300,
      ],
      'third_party_settings' => [],
      'weight' => 0,
      'region' => 'content',
    ]);

    try {
      // Save the display configuration.
      $view_display->save();
    }
    catch (EntityStorageException $e) {
      $logger->error(t('An error happened while saving the media_library view display for media type @machine_name. @exception_message', [
        '@machine_name' => $media_type_id,
        '@exception_message' => $e->getMessage(),
      ]));
    }
  }

  /**
   * Update the all view displays except media_library for the media type.
   *
   * @param \Drupal\media\MediaTypeInterface $media_type
   *   The media type.
   * @param array $config_data
   *   The config data.
   * @param string $media_type_id
   *   The media type ID.
   */
  protected static function updateViewDisplays(MediaTypeInterface $media_type, array $config_data, string $media_type_id): void {
    $config_factory = \Drupal::service('config.factory');
    // Exclude the `media_library` view modes form $view_displays.
    $view_displays = array_filter($config_factory->listAll('core.entity_view_display.media.' . $media_type_id), fn($view_displays) => substr($view_displays, -14) != '.media_library');

    // Determine which field to display and which to hide between
    // Asset reference and On-site asset storage fields
    // based on the method selected during migration.
    $fields = self::determineFieldNameToDisplay($media_type, $config_data);
    $field_name_to_display = $fields['display'];
    $field_name_to_hide = $fields['hide'];

    // Update all view displays except for the media library for the media type.
    foreach ($view_displays as $view_display) {
      $view_display_config = $config_factory->getEditable($view_display);
      $field = self::findFieldToReplace($view_display_config, $media_type_id);

      if ($field) {
        $field_settings = $view_display_config->get("content.$field");

        // If the embed_style is set, update the field settings.
        if (isset($fields['embed_style'])) {
          $field_settings['settings']['embed_style'] = $fields['embed_style'];
        }

        // If the media source is 'acquia_dam_asset:image', update embed style
        // from the existing field setting's image_style config.
        if ($media_type->get('source') == 'acquia_dam_asset:image' && !$config_data['sync_method']) {
          $field_settings['type'] = 'acquia_dam_embed_code';
          $field_settings['settings']['embed_style'] = empty($field_settings['settings']['image_style'])
            ? 'original' : $field_settings['settings']['image_style'];
        }

        // Update the view display.
        $view_display_config
          ->clear("content.$field")
          ->set("hidden.$field", TRUE)
          ->clear("content.$field_name_to_hide")
          ->set("hidden.$field_name_to_hide", TRUE)
          ->clear("hidden.$field_name_to_display")
          ->set("content.$field_name_to_display", $field_settings)
          ->save();
      }
    }
  }

  /**
   * Determine the field name to display.
   *
   * @param \Drupal\media\MediaTypeInterface $media_type
   *   The media type.
   * @param array $config_data
   *   The config data.
   * @param bool $modern_display
   *   Whether to use modern local storage or not.
   *
   * @return array
   *   The array of field name to display and hide.
   */
  protected static function determineFieldNameToDisplay(MediaTypeInterface $media_type, array $config_data, bool $modern_display = FALSE): array {
    $field = [];
    $reference_field = MediaSourceField::SOURCE_FIELD_NAME;
    $download_field = $media_type->get('source') == 'acquia_dam_asset:image' ? ManagedImageField::MANAGED_IMAGE_FIELD_NAME : ManagedFileField::MANAGED_FILE_FIELD_NAME;
    // Hide Asset Reference field and display On-site asset storage
    // when Sync option is selected.
    if ($config_data['sync_method'] == 'sync') {
      $field['hide'] = $reference_field;
      $field['display'] = $download_field;
    }
    // Hide On-site asset storage field and display Asset Reference
    // when an Embed option is selected.
    else {
      $field['hide'] = $download_field;
      $field['display'] = $reference_field;

      // Set the embed style based on the media type source.
      switch ($media_type->get('source')) {
        case 'acquia_dam_asset:audio':
          $field['embed_style'] = 'remote_streaming';
          break;

        case 'acquia_dam_asset:documents':
          $field['embed_style'] = 'original';
          break;

        case 'acquia_dam_asset:generic':
          $field['embed_style'] = 'link_download';
          break;

        case 'acquia_dam_asset:pdf':
          $field['embed_style'] = 'link_thumbnail_download';
          break;

        case 'acquia_dam_asset:spinset':
          $field['embed_style'] = 'link_text';
          break;

        case 'acquia_dam_asset:video':
          $field['embed_style'] = 'inline_view';
          break;
      }
    }

    return $field;
  }

  /**
   * Find the field to replace it.
   *
   * @param \Drupal\Core\Config\Config $view_mode_config
   *   The view mode configuration.
   * @param string $media_type_id
   *   The media type ID.
   *
   * @return string
   *   The field name to replace.
   */
  protected static function findFieldToReplace(Config $view_mode_config, string $media_type_id): string {
    // Get the old reference field name.
    $old_reference_field_names = [
      'field_acquiadam_asset_audio',
      'field_acquiadam_asset_doc',
      'field_acquiadam_asset_file',
      'field_acquiadam_asset_image',
      'field_acquiadam_asset_video',
    ];
    foreach ($view_mode_config->get('content') as $field_name => $field_config) {
      if (in_array($field_name, $old_reference_field_names)) {
        return $field_name;
      }
    }

    return '';
  }

  /**
   * Get total count of media items for a type.
   *
   * @param \Drupal\core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param string $media_type_id
   *   The media type ID.
   *
   * @return int
   *   The count of media items.
   */
  public static function getMediaTypeCount(EntityTypeManagerInterface $entity_type_manager, string $media_type_id): int {
    return $entity_type_manager->getStorage('media')->getQuery()
      ->condition('bundle', $media_type_id)
      ->count()
      ->accessCheck(false)
      ->execute();
  }

  /**
   * Copy asset ID from the legacy to the modern table.
   *
   * @param array $bundle_data
   *   The media migration information.
   *
   * @return int
   *   The number of updated records.
   */
  public static function copyAssetIdFromOldToNewTable(array $bundle_data) {
    $media_type_id = $bundle_data['media_type_id'];
    $sync_method = $bundle_data['sync_method'];
    $database = \Drupal::service('database');
    $batch_size = 1000;

    // First, check if both field tables exist
    $source_table = 'media__field_acquiadam_asset_id';
    $dest_table = 'media__acquia_dam_asset_id';
    $dest_revision_table = 'media_revision__acquia_dam_asset_id';

    // Get total count for progress tracking
    $total_query = $database->select($source_table, 's')
      ->condition('s.bundle', $media_type_id)
      ->countQuery();
    $total = $total_query->execute()->fetchField();

    $processed = 0;
    $updated = 0;

    while ($processed < $total) {
      // Get batch of source data
      $source_data = $database->select($source_table, 's')
        ->fields('s', ['bundle', 'deleted', 'entity_id', 'revision_id', 'langcode', 'delta', 'field_acquiadam_asset_id_value'])
        ->condition('s.bundle', $media_type_id)
        ->range($processed, $batch_size)
        ->execute()
        ->fetchAll();

      if (empty($source_data)) {
        break;
      }

      // Start transaction
      $transaction = $database->startTransaction();

      try {
        foreach ($source_data as $data) {
          $field_data = [
            'bundle' => $data->bundle,
            'deleted' => $data->deleted,
            'entity_id' => $data->entity_id,
            'revision_id' => $data->revision_id,
            'langcode' => $data->langcode,
            'delta' => $data->delta,
            'acquia_dam_asset_id_asset_id' => $data->field_acquiadam_asset_id_value,
          ];
          // Update main field table
          $database->insert($dest_table)
            ->fields($field_data)
            ->execute();

          // Update revision table if it exists
          if ($database->schema()->tableExists($dest_revision_table)) {
            $database->insert($dest_revision_table)
              ->fields($field_data)
              ->execute();
          }

          $updated++;
        }

        $processed += count($source_data);

      } catch (\Exception $e) {
        $transaction->rollBack();
        throw $e;
      }

    }

    // If the method is 'sync', copy asset data from legacy to modern table.
    if ($sync_method == 'sync') {
      // Get the source and revision tables for copying asset data.
      $fields = \Drupal::entityTypeManager()->getStorage('media_type')->load($media_type_id)->get('field_map');
      $media_field = $fields['file'];
      $source_data_table = 'media__' . $media_field;
      // Add operation to copy asset data from legacy to modern table.
      Self::copyAssetDataFromOldToNewTable($media_type_id, $media_field, $source_data_table);
    }

    // Retrieve the current state value for the migrated data variable.
    $migrated_data = \Drupal::state()->get('media_acquiadam.migrated_data', []);
    // Merge the migrated data with media type and media items count.
    $migrated_data = array_merge($migrated_data, [
      $media_type_id => [
        'media_type_label' => $bundle_data['media_type_label'],
        'sync_method' => $bundle_data['sync_method'],
        'target_source_type' => $bundle_data['target_source_type'],
        'migrated_assets_count' => $updated,
      ]
    ]);
    // Save the migrated data config.
    \Drupal::state()->set('media_acquiadam.migrated_data', $migrated_data);
  }

  /**
   * Copy asset data from the legacy to the modern table.
   *
   * @param string $media_type_id
   *   The media type ID.
   * @param string $media_field
   *   The media field name.
   * @param string $source_table
   *   The source table name.
   */
  public static function copyAssetDataFromOldToNewTable(string $media_type_id, string $media_field, string $source_table) {
    $database = \Drupal::service('database');
    $batch_size = 1000;

    // Get total count for progress tracking
    $total_query = $database->select($source_table, 's')
      ->condition('s.bundle', $media_type_id)
      ->countQuery();
    $total = $total_query->execute()->fetchField();

    $processed = 0;
    $updated = 0;
    $fields = [
      'bundle',
      'deleted',
      'entity_id',
      'revision_id',
      'langcode',
      'delta'
    ];

    // The destination tables for copying asset data.
    $dest_table = 'media__acquia_dam_managed_file';
    $dest_revision_table = 'media_revision__acquia_dam_managed_file';

    switch ($media_field) {
      case 'field_acquiadam_asset_audio':
      case 'field_acquiadam_asset_doc':
      case 'field_acquiadam_asset_file':
      case 'field_acquiadam_asset_video':
        $fields = array_merge($fields, [
          $media_field . '_target_id',
          $media_field . '_display',
          $media_field . '_description',
        ]);
        break;
      case 'field_acquiadam_asset_image':
        $dest_table = 'media__acquia_dam_managed_image';
        $dest_revision_table = 'media_revision__acquia_dam_managed_image';
        $fields = array_merge($fields, [
          'field_acquiadam_asset_image_target_id',
          'field_acquiadam_asset_image_alt',
          'field_acquiadam_asset_image_title',
          'field_acquiadam_asset_image_width',
          'field_acquiadam_asset_image_height'
        ]);
        break;
    }

    while ($processed < $total) {
      // Get batch of source data
      $source_data = $database->select($source_table, 's')
        ->fields('s', $fields)
        ->condition('s.bundle', $media_type_id)
        ->range($processed, $batch_size)
        ->execute()
        ->fetchAll();

      if (empty($source_data)) {
        break;
      }

      // Start transaction
      $transaction = $database->startTransaction();

      try {
        foreach ($source_data as $data) {
          switch ($media_field) {
            case 'field_acquiadam_asset_audio':
            case 'field_acquiadam_asset_doc':
            case 'field_acquiadam_asset_file':
            case 'field_acquiadam_asset_video':
              $field_data = [
                'bundle' => $data->bundle,
                'deleted' => $data->deleted,
                'entity_id' => $data->entity_id,
                'revision_id' => $data->revision_id,
                'langcode' => $data->langcode,
                'delta' => $data->delta,
                'acquia_dam_managed_file_target_id' => $data->{$media_field . '_target_id'},
                'acquia_dam_managed_file_display' => $data->{$media_field . '_display'},
                'acquia_dam_managed_file_description' => $data->{$media_field . '_description'}
              ];
              // Update main field table
              $database->insert($dest_table)
                ->fields($field_data)
                ->execute();

              // Update revision table if it exists
              if ($database->schema()->tableExists($dest_revision_table)) {
                $database->insert($dest_revision_table)
                  ->fields($field_data)
                  ->execute();
              }
              break;
            case 'field_acquiadam_asset_image':
              $field_data = [
                'bundle' => $data->bundle,
                'deleted' => $data->deleted,
                'entity_id' => $data->entity_id,
                'revision_id' => $data->revision_id,
                'langcode' => $data->langcode,
                'delta' => $data->delta,
                'acquia_dam_managed_image_target_id' => $data->field_acquiadam_asset_image_target_id,
                'acquia_dam_managed_image_alt' => $data->field_acquiadam_asset_image_alt,
                'acquia_dam_managed_image_title' => $data->field_acquiadam_asset_image_title,
                'acquia_dam_managed_image_width' => $data->field_acquiadam_asset_image_width,
                'acquia_dam_managed_image_height' => $data->field_acquiadam_asset_image_height
              ];
              // Update main field table
              $database->insert($dest_table)
                ->fields($field_data)
                ->execute();

              // Update revision table if it exists
              if ($database->schema()->tableExists($dest_revision_table)) {
                $database->insert($dest_revision_table)
                  ->fields($field_data)
                  ->execute();
              }
              break;
          }


          $updated++;
        }

        $processed += count($source_data);

      } catch (\Exception $e) {
        $transaction->rollBack();
        throw $e;
      }

    }
  }

  /**
   * Finish the migration process.
   */
  public static function finish(): void {
    // Update from entity browser to media library.
    self::entityBrowserToMediaLibrary();
    $deleted = $updated = [];
    // Retrieve the deleted media types.
    $deleted_media_types = \Drupal::state()->get('media_acquiadam.delete_media_type', []);
    if (!empty($deleted_media_types)) {
      foreach ($deleted_media_types as $message) {
        $deleted[] = t("@message", [
          '@message' => $message,
        ]);
      }
    }

    // Retrieve the updated media types.
    $updated_media_types = \Drupal::state()->get('media_acquiadam.migrated_data', []);
    if (!empty($updated_media_types)) {
      foreach ($updated_media_types as $media_type_id => $data) {
        $updated[] = t("Updated media type '@media_type_label' with '@count' media items using '@sync_method' method.", [
          '@media_type_label' => $data['media_type_label'],
          '@count' => $data['migrated_assets_count'],
          '@sync_method' => $data['sync_method'],
        ]);
      }
    }
    // Merge the summary message.
    $summary = array_merge($deleted, $updated);
    $message = implode(' ', $summary);
    $message .= 'Acquia DAM migration completed successfully.';
    // Prepare a summary of the operations performed.
    \Drupal::messenger()->addStatus(t('@message', ['@message' => $message]));
    // Register the fact that the migration has run already.
    \Drupal::state()->set('media_acquiadam.migration_process_finished', time());
  }

  /**
   * Update from entity browser to media library.
   */
  public static function entityBrowserToMediaLibrary(): void {
    $entity_type_manager = \Drupal::entityTypeManager();
    $dam_bundles_all = $entity_type_manager->getStorage('media_type')
      ->loadByProperties(['source_configuration.source_field' => 'field_acquiadam_asset_id']);
    $dam_bundles = array_map(fn($bundle) => $bundle->id(), $dam_bundles_all);

    // Get all form displays using the entity browser.
    $form_displays = $entity_type_manager->getStorage('entity_form_display')->loadByProperties();

    // Store field config.
    $entity_field_config = $entity_type_manager->getStorage('field_config');

    // Update the form displays.
    foreach ($form_displays as $key => $form_display) {
      // Look for the field that uses the entity browser formatter.
      foreach ($form_display->getComponents() as $field_name => $component) {
        [$entity_type, $bundle] = explode('.', $key);
        $field_config = $entity_field_config->load("{$entity_type}.{$bundle}.{$field_name}");
        if (!$field_config || $field_config->getType() !== 'entity_reference') {
          continue;
        }
        $target_bundles = $field_config->getSettings()['handler_settings']['target_bundles'] ?? [];
        if (!array_intersect($target_bundles, $dam_bundles)) {
          continue;
        }
        // Make sure entity presave and insert hooks doesn't get called.
        $form_display->setSyncing(TRUE);
        $component_type = $component['type'] ?? '';
        if ($component_type === 'entity_browser_entity_reference') {
          $component['type'] = 'media_library_widget';
          $component['settings'] = ['media_types' => $target_bundles];
          // Update the field type to use the media library formatter.
          $form_display->setComponent($field_name, $component)
            ->save();
        }
      }
    }
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc