cforge-2.0.x-dev/src/EventSubscriber/MigrationSubscriber.php

src/EventSubscriber/MigrationSubscriber.php
<?php

namespace Drupal\cforge\EventSubscriber;

use Drupal\mcapi\Entity\Storage\WalletStorage;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate\Event\MigratePreRowSaveEvent;
use Drupal\migrate\Event\MigratePostRowSaveEvent;
use Drupal\migrate\Event\MigrateImportEvent;
use Drupal\migrate\Row;
use Drupal\user\Entity\User;
use Drupal\user\Entity\Role;
use Drupal\field\Entity\FieldConfig;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\Core\Menu\MenuTreeParameters;


/**
 * Migration modifications
 *
 * Note the order of events in MigrateExecutable::import
 * Dispatch PRE_IMPORT
 *  $source->next
 *  $source->prepareRow
 *    hook_migrate_prepare_row
 *    hook_migrate_NAME_prepare_row
 *  while
 *    processRow
 *    Dispatch PRE_ROW_SAVE
 *    Dispatch POST_ROW_SAVE
 *  Dispatch POST_IMPORT
 *
 */
class MigrationSubscriber implements EventSubscriberInterface {

  private $moduleHandler;
  private $moduleInstaller;
  private $accountSwitcher;
  private $entityTypeManager;
  private $configFactory;
  private $menuTreeStorage;

  /**
   * @todo move this to the migration definitions in the relevant modules
   */
  const FILTER_FORMATS = [
    'editor_full_html' => 'full_html',
    'full_html' => 'full_html',
    'editor_filtered_html' => 'basic_html',
    'filtered_html' => 'basic_html',
    'plain_text' => '', // This is the fallback
    'php_code' => '', // This will be ignored
    ''  => 'full_html'
  ];

  /**
   * @todo move this to the migration definitions in the relevant modules
   */
  const VOCABS = [
    'cforge_docs_categories' => 'binders',
    'offers_wants_types' => NULL,
    'offers_wants_categories' => 'category',
    'galleries' => 'galleries'
  ];

  function __construct($module_handler, $module_installer, $account_switcher, $config_factory, $entity_type_manager, $menu_tree_storage) {
    $this->moduleHandler = $module_handler;
    $this->moduleInstaller = $module_installer;
    $this->accountSwitcher = $account_switcher;
    $this->configFactory = $config_factory;
    $this->entityTypeManager = $entity_type_manager;
    $this->menuTreeStorage = $menu_tree_storage;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() : array {
    return [
      'migrate.pre_import' => [['migratePreImport']],
      'migrate.pre_row_save' => [['migratePreRowSave']],
      'migrate.post_row_save' => [['migratePostRowSave']],
      'migrate.post_import' => [['migratePostImport']]
    ];
  }

  /**
   * @param Drupal\migrate\Event\MigrateImportEvent $event
   */
  public function migratePreImport(MigrateImportEvent $event) {
    $migration = $event->getMigration();
    switch ($migration->id()) {
      case 'd7_field_instance':
        // Find any duplicate field instances
        $db = $migration->getSourcePlugin()->getDatabase();
        $dupes = $db->query("SELECT min([id]) FROM {field_config_instance} WHERE [deleted] = 0 GROUP BY [entity_type], [field_name], [bundle] HAVING count([id]) > 1")->fetchCol();
        if ($dupes) {
          // Delete them from the ORIGINAL database
          $db->delete('field_config_instance')->condition('id', $dupes, 'IN')->execute();
        }
        break;
      case 'd7_user':
        // Because the wallet names based on user names are wrong if user is not logged in.
        $this->accountSwitcher->switchTo(User::load(1));
        break;
      case 'd7_node_complete:forum':
        if ($this->moduleHandler->moduleExists('forum_access')) {
          $this->moduleInstaller->install(['forum_access_migrate']);
        }
        break;
    }
  }

  /**
   * @param Drupal\migrate\Event\MigratePreRowSaveEvent $event
   */
  public function migratePreRowSave(MigratePreRowSaveEvent $event) {
    $row = $event->getRow();
    $migration = $event->getMigration();
    // Ensure the category->required setting is transferred.
    if ($migration->id() == 'd7_field_instance' and $row->getSourceProperty('field_name') == 'offers_wants_categories' and $row->getSourceProperty('entity_type') == 'transaction') {
      // Just take this one property which varied in some d7 sites then ditch the field
      $field = FieldConfig::load('mcapi_transaction.mcapi_transaction.category');
      $field->setRequired($row->getSourceProperty('required'))->save();
    }

    if ($migration->id() == 'd7_user') {
      $address = $row->getSourceProperty('profile_address');
      $dest_address = $this->transformAddress($address[0]);
      $row->setDestinationProperty('address', [$dest_address]);
      $row->removeDestinationProperty('profile_address');
      $phones = $row->getSourceProperty('profile_phones');
      $contactme = ['mob' => $phones[0]['value']];
      if (isset($phones[1])) {
        $contactme['tel'] = $phones[1]['value'];
      }
      $row->setDestinationProperty('contactme', $contactme);
    }

    if ($migration->id() == 'd7_field' or $migration->id() == 'd7_field_instance') {
      //Skip these d7 fields and rename them when importing the entities they belong to.
      $existing_fields = [
        'manage_notes',
        'manage_responsibility',
        'profile_address',
        'profile_notes',
        'profile_phones',
        'profile_location',
        'upload', //became 'attached'
        'upload_private', // Became 'attached_private',
        'user_picture'
        //'title', // Prevents 'base_field_override' fields being created
      ];

      if (in_array($row->getSourceProperty('field_name'), $existing_fields)) {
        throw new MigrateSkipRowException('Field has already installed equivalent');
      }
      if ($row->getSourceProperty('field_name') == 'body' and $row->getSourceProperty('entity_type') == 'node') {
        throw new MigrateSkipRowException('Node body fields not migrated');
      }
      if ($row->getSourceProperty('field_name') == 'manage_responsibility') {
        throw new MigrateSkipRowException("Field manage_responsibility is discontinued");
      }
      if ($row->getSourceProperty('field_name') == 'offers_wants_categories') {
        throw new MigrateSkipRowException('offers_wants_categories replacement is already installed by the smallads module');
      }
    }

    // All novel user fields should go on the 'profile' form display and 'default' display
    if ($migration->getPluginId() == 'd7_field_instance' and $row->getSourceProperty('entity_type') == 'user') {
      $formDisplay = EntityFormDisplay::load('user.user.profile');
      $formDisplay->setComponent($row->getSourceProperty('field_name'), ['weight' => 20])->save();
      $viewDisplay = EntityViewDisplay::load('user.user.default');
      $viewDisplay->setComponent($row->getSourceProperty('field_name'), ['weight' => 20])->save();
    }

    if ($migration->id() == 'd7_user') {
      // Don't migrate blocked users.
      if (!$row->getSourceProperty('status')) {
        if (!$this->hasTraded($migration->getSourcePlugin()->getDatabase(), $row->getSourceProperty('status'))) {
          throw new MigrateSkipRowException('Skipping blocked user '.$row->getSourceProperty('uid'));
        }
      }
      // Rename the fields
      $map = [
        'manage_notes' => 'notes_admin',
        'profile_notes' => 'notes',
        'profile_location' => 'coordinates', // will only save if cforge_geo
      ];
      foreach ($map as $old_name => $new_name) {
        if ($row->hasSourceProperty($old_name)) {
          $this->mapField($row, $old_name, $new_name);
        }
      }
      // Experimental
      $row->setDestinationProperty('timezone', NULL);
      // the commitee and local admin roles were not migrated so are not available for user migration to look up.
      if (in_array(4, $row->getSourceProperty('roles'))) {
        $dest_roles = $row->getDestinationProperty('roles');
        $dest_roles[] = 'committee';
        $row->setDestinationProperty('roles', $dest_roles);
      }

      $address = $row->getSourceProperty('profile_address');
      $dest_address = $this->transformAddress($address[0]);
      $row->setDestinationProperty('address', [$dest_address]);
      $row->removeDestinationProperty('profile_address');
    }

    if ($migration->id() == 'd7_mcapi_transaction') {
      $this->mapField($row, 'offers_wants_categories', 'category');
    }

    if ($migration->id() == 'd7_menu') {
      if ($row->getSourceProperty('menu_name') == 'visitors') {
        throw new MigrateSkipRowException('Not migrating visitors menu.');
      }
    }

    // Move all terms that used to be in offers_wants_categories field.
    if ($migration->getPluginId() == 'd7_node' or $migration->id() == 'd7_mcapi_transaction') {
      if ($row->hasDestinationProperty('offers_wants_categories')) {
        $entity_type = $row->getSourceProperty('entity_type');
        $new_field_name = $entity_type == 'node' ? 'categories' : 'category';
        $this->mapField($row, 'offers_wants_categories', $new_field_name);
      }
    }

    // Change the filter format for every body field
    // I don't know why this doesn't happen already
    if ($row->getSourceProperty('plugin') == 'd7_node') {
      $this->entityBodyFilterFormat($row, 'body');

      if ($row->hasSourceProperty('upload')) {
        $this->mapFileField($row, 'upload', 'attached');
      }
      if ($row->hasSourceProperty('upload_private')) {
        $this->mapFileField($row, 'upload_private', 'attached_private');
      }
    }
    elseif ($row->getSourceProperty('plugin') == 'd7_smallad') {
      $this->entityBodyFilterFormat($row, 'body');
    }
    elseif ($migration->id() == 'd7_custom_block') {
      // I don't know why this isn't happening automatically - perhaps a bug in d8.4?
      $row->setDestinationProperty('body/format', $this->convertFormat($row->getDestinationProperty('body/format'), 'full_html'));
    }
    // Comments
    elseif ($migration->id() == 'd7_comment_type') {
      if ($row->getSourceProperty('type') != 'forum') {
        throw new MigrateSkipRowException('Comments already exist');
      }
    }
    elseif ($migration->id() == 'd7_comment_field') {
      $row->setDestinationProperty('type', 'comments');
    }
    elseif ($migration->id() == 'd7_comment') {
      $this->entityBodyFilterFormat($row, 'comment_body');
      if ($row->getDestinationProperty('entity_type') == 'node') {
        if ($row->getSourceProperty('node_type') != 'forum') {
          $row->setDestinationProperty('comment_type', $row->getDestinationProperty('entity_type'));
          $row->setDestinationProperty('field_name', 'comments');
        }
      }
    }
    // Fix an error I can't explain
    elseif ($migration->id() == 'd7_field_formatter_settings') {
      if (empty($row->getDestinationProperty('view_mode'))) {
        throw new MigrateSkipRowException('Skipping because view mode is lost.');
      }
    }

    // Since we're not migrating the address field, we lose the country value in
    // its widget settings, so here we try to take the country from the d7 site
    // variable.
    //@todo ensure that d7 sites have the site_default_country variable populated..
    elseif ($migration->id() == 'd7_system_date') {
      // France as a default is a guess based on usage stags
      $default_country = $row->getSourceProperty('site_default_country');
      if (strlen($default_country) <> 2){
        \Drupal::logger('cforge')->error(print_r($row->getSource(), 1));
        die(' -- Invalid country code! died.');
      }
      // @todo some sites don't have this var set so what to do?
      FieldConfig::load('user.user.address')
        ->setSetting('available_countries', [$default_country])
        ->setSetting('default_country', $default_country)
        ->save();
    }
    elseif ($migration->id() == 'd7_user_role') {
      $id = $row->getDestinationProperty('id');
      if ($id == 'anonymous' or $id == 'authenticated') {
        $permissions = $row->getDestinationProperty('permissions');
        $permissions[] = 'use text format basic_html';
        $row->setDestinationProperty('permissions', array_unique($permissions));
      }
    }
    // This fixes a wierd incompatibility that just happens with a few blocks, leaving them with hidden titles.
    elseif ($migration->id() == 'd7_block') {
      if ($row->getSourceProperty('module') == 'user') {
        if ($row->getSourceProperty('delta') == 'new') {
          $settings = $row->getDestinationProperty('settings');
          if ($row->getSourceProperty('title') == '' and !$settings['label_display']) {
            $settings['label_display'] = 1;
            $row->setDestinationProperty('settings', $settings);
          }
        }
      }
      else {
        throw new MigrateSkipRowException();
      }
    }
    // Only migrate from certain menus
    elseif ($migration->id() == 'd7_menu_links') {
      // Move everything in visitors menu to main menu.
      if ($row->getSourceProperty('menu_name') == 'visitors') {
        $row->setDestinationProperty('menu_name', 'main');
      }
      // don't migrate unknown menus.
      $menu_name = $row->getSourceProperty('menu_name');
      if (!in_array($menu_name, ['footer', 'main-menu', 'secondary-menu', 'visitors'])) {
        throw new MigrateSkipRowException('Not migrating '.$row->getSourceProperty('link_path').' in '.$menu_name);
      }
      else {
        // Don't migrate if a link already exists in the same menu with the same title.
        $title = $row->getSourceProperty('link_title');
        $tree = $this->menuTreeStorage
          ->loadTreeData($row->getDestinationProperty('menu_name'), new MenuTreeParameters());
        foreach ($tree['tree'] as $id => $item) {
          if ($item['definition']['title'] == $title) {
            throw new MigrateSkipRowException($row->getSourceProperty('link_path') . ' already exists as a menu item');
          }
        }
      }
    }
    elseif ($migration->id() == 'd7_system_theme??') {
      // I can't see which migration contains the admin theme, which should be set to claro.
    }
  }


  /**
   * @param MigratePostRowSaveEvent $event
   * Save the user's avatar image which didn't migrate automatically
   */
  function migratePostRowSave(MigratePostRowSaveEvent $event) {
    $mig_id = $event->getMigration()->id();
    $row = $event->getRow();
    if ($mig_id == 'd7_user') {
      if ($pic_id = $row->getSourceProperty('picture')) {
        User::load($row->getSourceProperty('uid'))
          ->set('user_picture', $pic_id)
          ->save();
      }
    }
    elseif ($mig_id == 'd7_menu_links') {
      $old_name = $row->getSourceProperty('menu_name');
      // set the public flag on nodes with menu items from secondary-menu or visitors.
      if ($old_name == 'secondary-menu'or $old_name == 'visitors') {
        $path = $row->getSourceProperty('link_path');
        if (preg_match('/node\/([0-9]+)/', $path, $matches)) {
          \Drupal::keyValue('publiconly')->set($matches[1], 1);
        }
      }
    }
    elseif ($mig_id == 'd7_cforge_settings') {
      // Todo test this...
      if ($fee = $row->getSourceProperty('cforge_referrer_fee')) {
        print "\n check migration of referrer fee in \Drupal\cforge\MigrationSubscriber::migratePostRowSave\n";
        print print_r($row->getSource(), 1);
        if ($fee['value']) {
          \Drupal::service('module_installer')->install(['cforge_referrer']);
        }
      }
    }
  }

  /**
   * @param MigrateImportEvent $event
   */
  function migratePostImport(MigrateImportEvent $event) {
    switch ($event->getMigration()->id()) {
      case 'd7_filter_format':
        $this->configFactory->getEditable('filter.settings')->set('fallback_format', 'plain_text');
        break;
      case 'd7_mcapi_transaction':
        $this->removeSystemRole();
        break;
      case 'd7_node_complete:forum':
        $this->moduleInstaller->uninstall(['forum_access_migrate']);
        break;
      case 'd7_node:page':
        \Drupal::moduleHandler()->loadInclude('cforge', 'install');
        cforge_import_content('cforge', 'all'); // 403 and 403 Pages
    }
  }

  /**
   * Delete all the accounts with the system role and delete the system role.
   * @note this assumes that wallets have been created.
   */
  private function removeSystemRole() {
    $user_storage = $this->entityTypeManager->getStorage('user');
    $system_users = $user_storage->loadByProperties(['roles' => 'system']);
    $committee = $user_storage->loadByProperties(['roles' => 'committee']);
    $ladmins = $user_storage->loadByProperties(['roles' => 'local admin']);
    unset($system_users[1], $committee[1], $ladmins[1]);
    $ladmin = reset($ladmins) or $ladmin = array_shift($committee);
    foreach ($system_users as $account) {
      foreach (WalletStorage::walletsOf($account, TRUE) as $wallet) {
        // Move it to local admin, with committee members having access
        $wallet->set('holder', $ladmin);
        if ($this->moduleHandler->moduleExists('mcapi_bursers')) {
          foreach ($committee as $comm) {
            $wallet->bursers->addBurser($comm);
          }
          $wallet->save();
        }
        print "\nChanged owner and added bursers to former system wallet ".$wallet->label();
      }
      print "\nDeleted former system user ".$account->getDisplayName();
      $account->delete();
    }
    if ($role = Role::load('system')){
      $role->delete();
      print "\nDeleted system role";
    }
  }

  /**
   * Change the destination name of a field.
   * @param Row $row
   * @param string $old_name
   * @param string $new_name
   */
  private function mapField(Row $row, string $old_name, string $new_name) {
    $row->setDestinationProperty($new_name, $row->getSourceProperty($old_name));
    $row->removeDestinationProperty($old_name);
  }

  private function mapFileField(Row $row, string $old_name, string $new_name) {
    $this->mapField($row, $old_name, $new_name);
    $files = $row->getDestinationProperty($new_name);
    foreach ($files as &$file) {
      $file['target_id'] = $file['fid'];
    }
    $row->setDestinationProperty($new_name, $files);
  }

  /**
   * Convert the filter format of a body field
   * @param Row $row
   * @param string $body_field_name
   * @param type $default
   */
  private function entityBodyFilterFormat(Row $row, $body_field_name, $default = 'basic_html') {
    if ($bods = $row->getDestinationProperty($body_field_name)) {
      foreach ($bods as $delta => &$body) {
        $old = $body['format']?? 'filtered_html';
        if (strpos($body['value'], '<embed')) {
          $old = 'full_html';
        }
        $body['format'] = $this->convertFormat($old, $default);
      }
      $row->setDestinationProperty($body_field_name, $bods);
    }
  }

  /**
   * Look up the new filter format witih the old
   */
  private function convertFormat($old_format_name, $default) {
    return static::FILTER_FORMATS[$old_format_name] ?? $default;
  }

  private function hasTraded($db, $uid) {
    return $db->query("SELECT count([xid]) FROM mcapi_transactions WHERE [payer] = $uid or [payee] = $uid")->fetchField();
  }

  // All this is necessary because the address/src/Plugin/migrate/process/AddressField.php isn't running
  private function transformAddress($value) {
    $parsed = [
      'country_code' => $value['country'],
      'administrative_area' => $value['administrative_area'],
      'locality' => $value['locality'],
      'dependent_locality' => $value['dependent_locality'],
      'postal_code' => $value['postal_code'],
      'sorting_code' => '',
      'address_line1' => $value['thoroughfare'],
      'address_line2' => $value['premise'],
      'organization' => $value['organisation_name'],
    ];
    if (!empty($value['first_name']) || !empty($value['last_name'])) {
      $parsed['given_name'] = $value['first_name'];
      $parsed['family_name'] = $value['last_name'];
    }
    elseif (!empty($value['name_line'])) {
      $split = explode(" ", $value['name_line']);
      $parsed['given_name'] = array_shift($split);
      $parsed['family_name'] = implode(' ', $split);
    }
    return $parsed;
  }

}

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

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