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();
}
}
}
}
}
