SU-SWS/stanford_migrate

View on GitHub
stanford_migrate.module

Summary

Maintainability
Test Coverage
<?php

/**
 * @file
 * Contains stanford_migrate.module.
 */

use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\migrate_plus\Entity\Migration;
use Drupal\node\NodeInterface;
use Drupal\ultimate_cron\CronJobInterface;

/**
 * Implements hook_help().
 */
function stanford_migrate_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    // Main module help for the stanford_migrate module.
    case 'help.page.stanford_migrate':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Adds more functionality to migrate and migrate plus modules') . '</p>';
      return $output;

    default:
  }
}

/**
 * Implements hook_migrate_prepare_row().
 */
function stanford_migrate_migrate_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
  // Oauth2 authentication adds a token query parameter into the data urls that changes frequently. Since it
  // changes, the hash of the row  changes without any data actually changing. Fix the row data so that it doesn't
  // contain those tokens.
  $authentication = $row->getSourceProperty('authentication/plugin');
  $current_url = $row->getSourceProperty('current_feed_url');
  if ($current_url && $authentication == 'oauth2') {
    $row->setSourceProperty('current_feed_url', preg_replace('/access_token=.*?&/', '', $current_url));
  }
  // In case the list of urls changes dynamically, lets just remove it from the source data to avoid unnecessary hash
  // changes.
  $row->setSourceProperty('urls', []);
}

/**
 * Get the migration that imported the given node.
 *
 * @param \Drupal\node\NodeInterface $node
 *   Node entity.
 *
 * @return array|\Drupal\migrate_plus\Entity\MigrationInterface|mixed
 *   Migration entity or null/false if none found.
 *
 * @deprecated in 8.2.3 and is removed in 9.0.0. Use \Drupal::service('stanford_migrate')->getNodesMigration() instead.
 */
function stanford_migrate_get_migration(NodeInterface $node) {
  return \Drupal::service('stanford_migrate')->getNodesMigration($node);
}

/**
 * Implements hook_entity_form_display_alter().
 */
function stanford_migrate_entity_form_display_alter(EntityFormDisplayInterface $form_display, array $context) {
  // We only care about nodes, but this could be expanded later if more entities
  // are imported.
  $node = \Drupal::routeMatch()->getParameter('node');
  if ($context['entity_type'] != 'node' || !$node) {
    return;
  }

  $migration = \Drupal::service('stanford_migrate')->getNodesMigration($node);
  // Check if the current node was imported.
  if (!$migration) {
    return;
  }

  // Grab the default display settings for use later.
  $default_display = EntityViewDisplay::load("node.{$node->bundle()}.default");

  $field_definitions = $form_display->get('fieldDefinitions');
  foreach ($form_display->getComponents() as $field_name => $component) {
    // Make sure the field component is one of the field definitions.
    if (empty($field_definitions[$field_name])) {
      continue;
    }

    // When edit an existing node that was imported via migrate module, mark the
    // fields that are mapped from migration as readonly.
    $field_definition = $field_definitions[$field_name];
    $columns = $field_definition->getFieldStorageDefinition()->getColumns();
    $processing = !empty($migration->process[$field_name]) || !empty($migration->process["$field_name/0"]);

    // This will check if a migrate process is mapped to a specific column on
    // the field.
    foreach (array_keys($columns) as $column) {
      $processing = $processing ?: !empty($migration->process["$field_name/$column"]) || !empty($migration->process["$field_name/0/$column"]);
    }

    // If the migration destination has the `overwrite_properties` configured,
    // those fields specifically should be locked, not the other fields that
    // are not designated in the original process configuration.
    if ($processing && !empty($migration->get('destination')['overwrite_properties'])) {
      // If the current field doesn't exist in the overwrite_properties, it
      // should not be considered to be processing since it's a one time only
      // import.
      $processing = FALSE;

      foreach ($migration->get('destination')['overwrite_properties'] as $overwrite_property) {
        // If any part of the field is set to overwrite, lock the whole field
        // down.
        $overwrite_property = strstr($overwrite_property, '/', TRUE) ?: $overwrite_property;
        if ($field_name == $overwrite_property) {
          $processing = TRUE;
        }
      }
    }

    if ($processing) {
      \Drupal::messenger()
        ->addWarning(t('Some fields can not be edited since they contain imported & synced data.'));

      // If the default display is configured with some settings, let's use that
      // for the best display on the entity form. If it's not configured, the
      // readonly_field_widget module will use some default display settings.
      if ($display_component = $default_display->getComponent($field_name)) {
        $component['settings']['formatter_type'] = $display_component['type'];
        $component['settings']['formatter_settings'][$display_component['type']] = $display_component['settings'];
        $component['settings']['formatter_third_party_settings'] = $display_component['third_party_settings'] ?? [];

        // Add the empty fields module settings to display a message.
        $component['settings']['formatter_third_party_settings']['empty_fields']['handler'] = 'text';
        $component['settings']['formatter_third_party_settings']['empty_fields']['settings']['empty_text'] = '<em>' . t('No Data') . '</em>';
        $component['settings']['formatter_third_party_settings']['stanford_migrate']['readonly'] = TRUE;
      }
      $component['type'] = 'readonly_field_widget';
      $form_display->setComponent($field_name, $component);
    }
  }
}

/**
 * Implements hook_preprocess_HOOK().
 */
function stanford_migrate_preprocess_field(&$variables) {
  if ($variables['element']['#third_party_settings']['stanford_migrate']['readonly'] ?? false) {
    // Wrap the readonly form fields with classes so that they can be identified
    // more easily to the user.
    $variables['attributes']['class'][] = 'messages';
    $variables['attributes']['class'][] = 'messages--warning';
    $variables['attributes']['class'][] = 'messages--readonly';
    $variables['#attached']['library'][] = 'stanford_migrate/readonly';
  }
}

/**
 * Implements hook_config_readonly_whitelist_patterns().
 */
function stanford_migrate_config_readonly_whitelist_patterns() {
  $configs = [];
  /** @var \Drupal\migrate_plus\Entity\MigrationInterface $migration */
  foreach (Migration::loadMultiple() as $migration) {
    if ($migration->get('source')['plugin'] == 'csv') {
      $configs[] = $migration->getConfigDependencyName();
    }
  }
  return $configs;
}

/**
 * Migration callback to just get the current timestamp.
 *
 * We use this function in migration callback processes because using `time` as
 * the callback produces messages about "function accepts 0 arguments, 1
 * argument passed". So we just have our own callback that takes the argument
 * from the migration process and does nothing with it.
 *
 * @param mixed $arg
 *   Passed parameter from migration plugin `callback`.
 *
 * @return int
 *   Current timestamp.
 *
 * @see \Drupal\migrate\Plugin\migrate\process\Callback::transform()
 */
function _stanford_migrate_get_time($arg = NULL) {
  return time();
}

/**
 * Implements hook_entity_type_alter().
 */
function stanford_migrate_entity_type_alter(array &$entity_types) {
  if (\Drupal::moduleHandler()->moduleExists('migrate_source_csv')) {
    $entity_types['migration']->setFormClass('csv-upload', 'Drupal\stanford_migrate\Form\StanfordMigrateCsvImportForm');
    $entity_types['migration']->setLinkTemplate('csv-upload', '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/csv-upload');
    $entity_types['migration']->setLinkTemplate('csv-template', '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/csv-template');
  }
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 */
function stanford_migrate_migration_delete(Migration $entity) {
  // Clean up the state if the migration is deleted.
  \Drupal::state()->delete("stanford_migrate.csv.{$entity->id()}");
}

/**
 * Implements hook_migrate_id_map_info_alter().
 */
function stanford_migrate_migrate_id_map_info_alter(&$definitions) {
  $definitions['sql']['class'] = '\Drupal\stanford_migrate\Plugin\migrate\id_map\StanfordSql';
}

/**
 * Implements hook_migrate_source_info_alter().
 */
function stanford_migrate_migrate_source_info_alter(array &$definitions) {
  $definitions['url']['class'] = '\Drupal\stanford_migrate\Plugin\migrate\source\StanfordUrl';
}

/**
 * Implements hook_data_parser_info_alter().
 */
function stanford_migrate_data_parser_info_alter(array &$definitions) {
  $definitions['json']['class'] = '\Drupal\stanford_migrate\Plugin\migrate_plus\data_parser\StanfordJson';
  $definitions['simple_xml']['class'] = '\Drupal\stanford_migrate\Plugin\migrate_plus\data_parser\StanfordSimpleXml';
}

/**
 * Implements hook_migrate_process_info_alter().
 */
function stanford_migrate_migrate_process_info_alter(array &$definitions) {
  if (!empty($definitions['file_import'])) {
    $definitions['file_import']['class'] = '\Drupal\stanford_migrate\Plugin\migrate\process\StanfordFileImport';
  }
}

/**
 * Implements hook_entity_delete().
 *
 * When an entity is manually deleted from the database, we want to remove it
 * from the migration mapping.
 */
function stanford_migrate_entity_delete(EntityInterface $entity) {
  \Drupal::service('stanford_migrate')->deleteEntityFromMigration($entity);
}

/**
 * Ultimate cron callback function to execute a migration group.
 *
 * The entity id should be in the form `stanford_migrate_{migrate_group_id}` to
 * execute that group.
 *
 * @param \Drupal\ultimate_cron\CronJobInterface $cron_entity
 *   Ultimate cron entity.
 */
function stanford_migrate_ultimate_cron_task(CronJobInterface $cron_entity) {
  // Invalidate migration plugins to gather any changes to config entities
  // before running import. This allows for any changes to the source urls.
  \Drupal::service('plugin.manager.migration')->clearCachedDefinitions();
  Cache::invalidateTags(['migration_plugins']);

  /** @var \Drupal\stanford_migrate\StanfordMigrateInterface $migrate_service */
  $migrate_service = \Drupal::service('stanford_migrate');
  $migrations = $migrate_service->getMigrationList();

  $migration_group = str_replace('stanford_migrate_', '', $cron_entity->id());
  // Execute the migration entities in the provided migration group.
  if ($migration_group && !empty($migrations[$migration_group])) {
    return array_walk($migrations[$migration_group], [
      $migrate_service,
      'executeMigration',
    ]);
  }
  \Drupal::logger('stanford_migrate')
    ->info('No migration group @group exists. No migration executed.', ['@group' => $migration_group]);
}

/**
 * Executes a single migration, taken from drush command in migrate_tools.
 *
 * @param \Drupal\migrate\Plugin\MigrationInterface $migration
 *   The migration to execute.
 * @param string $migration_id
 *   The migration ID (not used, just an artifact of array_walk()).
 * @param array $options
 *   Array of options to pass into the migration import.
 * @param bool $batch
 *   Execute the migration using a batch process.
 *
 * @deprecated in 8.2.3 and is removed in 9.0.0. Use \Drupal::service('stanford_migrate')->executeMigration() instead.
 *
 * @see \Drupal\migrate_tools\Drush\MigrateToolsCommands::executeMigration()
 */
function stanford_migrate_execute_migration(MigrationInterface $migration, string $migration_id, array $options = [], bool $batch = FALSE): void {
  \Drupal::service('stanford_migrate')
    ->executeMigration($migration, $migration_id, $options, $batch);
}

/**
 * Retrieve a list of active migrations, partially taken from migrate_tools.
 *
 * @return \Drupal\migrate\Plugin\MigrationInterface[][]
 *   An array keyed by migration group, each value containing an array of
 *   migrations or an empty array if no migrations match the input criteria.
 *
 * @deprecated in 8.2.3 and is removed in 9.0.0. Use \Drupal::service('stanford_migrate')->getMigrationList() instead.
 *
 * @see \Drupal\migrate_tools\Drush\MigrateToolsCommands::migrationsList()
 */
function stanford_migrate_migration_list() {
  return \Drupal::service('stanford_migrate')->getMigrationList();
}