Commit 5582e6d6 authored by Yash Rode's avatar Yash Rode Committed by Paulo Henrique Cota Starling
Browse files

Issue #3245109 by yash.rode, srishtiiee, Wim Leers, paulocs, huzooka: Migrate...

Issue #3245109 by yash.rode, srishtiiee, Wim Leers, paulocs, huzooka: Migrate policies from Drupal 7 to Drupal 9
parent f69b3256
Loading
Loading
Loading
Loading
+33 −0
Changes for migrations/password_policy_settings.yml: 33 added lines, 0 removed lines.
Original line number Diff line number Diff line
id: password_policy_settings
label: Password policy configuration
migration_tags:
  - Drupal 7
  - Configuration
source:
  plugin: password_policy
process:
  id:
    -
      plugin: machine_name
      source: name
  password_policy_constraints:
    -
      plugin: default_value
      source: config
      default_value: "a:0:{}"
    -
      plugin: callback
      callable: unserialize
    -
      plugin: policy_configuration
  label: '@password_policy_constraints/label'
  policy_constraints: '@password_policy_constraints/constraints'
  roles: '@password_policy_constraints/roles'
  password_reset: '@password_policy_constraints/password_reset'
  send_reset_email: '@password_policy_constraints/send_reset_email'
  send_pending_email: '@password_policy_constraints/days'
destination:
  plugin: entity:password_policy
migration_dependencies:
  required:
    - d7_user_role
+275 −0
Changes for src/Plugin/migrate/process/PolicyConfiguration.php: 275 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Drupal\password_policy\Plugin\migrate\process;

use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\MigrateLookupInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Maps D7 policy settings to D9 values.
 *
 * @MigrateProcessPlugin(
 *   id = "policy_configuration"
 * )
 */
class PolicyConfiguration extends ProcessPluginBase implements ContainerFactoryPluginInterface {

  /**
   * The currently running migration.
   *
   * @var \Drupal\migrate\Plugin\MigrationInterface
   */
  protected $migration;

  /**
   * The migrate lookup service.
   *
   * @var \Drupal\migrate\MigrateLookupInterface
   */
  protected $migrateLookup;

  /**
   * Constructs a PolicyConfiguration object.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\migrate\Plugin\MigrationInterface $migration
   *   The currently running migration.
   * @param \Drupal\migrate\MigrateLookupInterface $migrate_lookup
   *   The migrate lookup service.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrateLookupInterface $migrate_lookup) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->migration = $migration;
    $this->migrateLookup = $migrate_lookup;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $migration,
      $container->get('migrate.lookup'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
    $source_version = $row->getSourceProperty('source_version');
    foreach ($value['role']['roles'] as $role) {
      // Checks if the policy applies to the role being considered.
      if ($role != 0) {
        $lookup_result = $this->migrateLookup->lookup('d7_user_role', [$role]);
        // Excludes 'anonymous' user role.
        if ($lookup_result['0']['id'] != "anonymous") {
          $value['roles'][$lookup_result['0']['id']] = $lookup_result['0']['id'];
        }
      }
    }
    unset($value['role']);
    // Check if the source site's Password Policy version is 7.x-1.x or 7.x-2.x.
    if ($source_version === 2) {
      $this->transformV2($row, $value);
    }
    else {
      $this->transformV1($value);
    }
    return $value;
  }

  /**
   * Function to convert unit of duration to days.
   *
   * @param string $value
   *   Time in which password expires.
   *
   * @return int
   *   Password expiry in number of days.
   */
  protected static function convert(string $value): int {
    $arr = explode(" ", $value);
    $days = (int) $arr[0];
    if ($arr[1] == "months") {
      $days = $days * 30;
    }
    elseif ($arr[1] == "weeks") {
      $days = $days * 7;
    }
    elseif ($arr[1] == "hours") {
      $days = round($days / 24);
    }
    return $days;
  }

  /**
   * Helper function for version2.
   *
   * @param \Drupal\migrate\Row $row
   *   The row from the source to process.
   * @param $value
   *   The source value for the migration process plugin.
   */
  private function transformV2(Row $row, &$value) {
    $value['label'] = $row->getSourceProperty('name');
    $value['send_reset_email'] = (bool) $value['expire']['expire_enabled'];
    if ($value['expire']['expire_enabled'] == 1) {
      // The initial format of number of days before a password expire warning
      // email is sent is a comma separated string. A minus sign before the
      // number indicates that the email is to be sent BEFORE expiration.
      // For example: "-14 days, -7 days, -2 days".
      $value['password_reset'] = $this->convert($value['expire']['expire_limit']);
      $value['days'] = explode(", ", $value['expire']['expire_warning_email_sent']);
      $value['days'] = str_replace(" days", "", $value['days']);
      $value['days'] = str_replace("-", "", $value['days']);
      $value['days'] = array_map('intval', $value['days']);
      // After transformation, we get an array of positive integers(no. of days)
      // [14,7,2].
    }

    $value['constraints'] = [
      [
        "id" => "password_length",
        "character_length" => $value['char_count']['char_count'],
        "character_operation" => "minimum",
      ],
      [
        "id" => "consecutive",
        "max_consecutive_characters" => $value['consecutive']['consecutive_char_count'],
      ],
      [
        "id" => "password_policy_character_constraint",
        "character_count" => $value['int_count']['int_count'],
        "character_type" => "numeric",
      ],
      [
        "id" => "password_policy_character_constraint",
        "character_count" => $value['alpha_count']['alpha_count'],
        "character_type" => "letter",
      ],
      [
        "id" => "password_policy_character_constraint",
        "character_count" => $value['special_count']['special_count'],
        "character_type" => "special",
      ],
    ];
    if ($value['username']['username'] === 1) {
      array_push($value['constraints'], [
        "id" => "password_username",
        "disallow_username" => 1,
      ],
      );
    }
  }

  /**
   * Helper function for version1.
   *
   * @param array $value
   *   The source value for the migration process plugin.
   */
  private function transformV1(array &$value) {
    $value['label'] = $value['name'];
    $value['password_reset'] = $value['expiration'];
    $value['send_reset_email'] = ($value['warning'] !== 0) ? 1 : 0;
    $value['days'] = explode(',', $value['warning']);
    $unserialize_constraints = unserialize($value["constraints"]);
    unset($value['constraints']);
    $policy_constraints_index = 0;
    foreach ($unserialize_constraints as $constraint => $count) {
      switch ($constraint) {
        case 'uppercase':
          $value['constraints'][$policy_constraints_index++] = [
            "id" => "password_policy_character_constraint",
            "character_count" => $count,
            "character_type" => "uppercase",
          ];
          break;

        case 'digit':
        case 'digit_placement':
          $value['constraints'][$policy_constraints_index++] = [
            "id" => "password_policy_character_constraint",
            "character_count" => $count,
            "character_type" => "numeric",
          ];
          break;

        case 'history':
          $value['constraints'][$policy_constraints_index++] = [
            "id" => "password_policy_history_constraint",
            "history_repeats" => $count,
          ];
          break;

        case 'character_types':
          $value['constraints'][$policy_constraints_index++] = [
            "id" => "character_types",
            "character_types" => $count,
          ];
          break;

        case 'username':
          $value['constraints'][$policy_constraints_index++] = [
            "id" => "password_username",
            "disallow_username" => ($count !== 0) ? 1 : 0,
          ];
          break;

        case 'length':
          $value['constraints'][$policy_constraints_index++] = [
            "id" => "password_length",
            "character_length" => $count,
            "character_operation" => "minimum",
          ];
          break;

        case 'delay':
          $value['constraints'][$policy_constraints_index++] = [
            "id" => "password_policy_delay_constraint",
            "delay" => $count,
          ];
          break;

        case 'punctuation':
          $value['constraints'][$policy_constraints_index++] = [
            "id" => "password_policy_character_constraint",
            "character_count" => $count,
            "character_type" => "special",
          ];
          break;

        case 'lowercase':
          $value['constraints'][$policy_constraints_index++] = [
            "id" => "password_policy_character_constraint",
            "character_count" => $count,
            "character_type" => "lowercase",
          ];
          break;

        case 'letter':
          $value['constraints'][$policy_constraints_index++] = [
            "id" => "password_policy_character_constraint",
            "character_count" => $count,
            "character_type" => "letter",
          ];
          break;
      }
    }
  }

}
+82 −0
Changes for src/Plugin/migrate/source/PasswordPolicy.php: 82 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Drupal\password_policy\Plugin\migrate\source;

use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;

/**
 * Password policy migrate source plugin.
 *
 * @MigrateSource(
 *   id = "password_policy",
 *   source_module = "password_policy",
 * )
 */
class PasswordPolicy extends DrupalSqlBase {

  /**
   * {@inheritdoc}
   */
  public function fields() {
    return [
      'name' => $this->t('Unique name'),
      'config' => $this->t('Configuration'),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getIds() {
    return [
      'name' => [
        'type' => 'string',
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function query() {
    return $this->select('password_policy', 'password_policy')
      ->fields('password_policy');
  }

  /**
   * {@inheritDoc}
   */
  public function prepareRow(Row $row) {
    $system_data = $this->getSystemData();
    $password_policy_schema = $system_data['module']['password_policy']['schema_version'] ?? 0;
    $source_password_policy_version = 1;
    if ((int) $password_policy_schema >= 7200) {
      $source_password_policy_version = 2;
    }
    else {
      $source_data = $row->getSource();
      $password_policy_role = $this->select('password_policy_role', 'role')
        ->fields('role')
        ->condition('pid', $source_data['pid'])
        ->execute()
        ->fetchAll();
      $config['pid'] = $source_data['pid'];
      $config['name'] = $source_data['name'];
      $config['description'] = $source_data['description'];
      $config['enabled'] = $source_data['enabled'];
      $config['constraints'] = $source_data['constraints'];
      $config['created'] = $source_data['created'];
      $config['expiration'] = $source_data['expiration'];
      $config['warning'] = $source_data['warning'];
      $config['weight'] = $source_data['weight'];
      foreach ($password_policy_role as $role) {
        $config['role']['roles'][$role['rid']] = $role['rid'];
      }
      $row->setSourceProperty('config', serialize($config));
    }
    $row->setSourceProperty('source_version', $source_password_policy_version);
    return parent::prepareRow($row);
  }

}
+18 −0
Changes for tests/fixtures/drupal7.1.php: 18 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php
// phpcs:ignoreFile
/**
 * @file
 * A database agnostic dump for testing purposes.
 *
 * This file was generated by the Drupal 9.2.6 db-tools.php script.
 */

use Drupal\Core\Database\Database;

$connection = Database::getConnection();

include 'drupal7.1' . DIRECTORY_SEPARATOR . 'password_policy.php';
include 'drupal7.1' . DIRECTORY_SEPARATOR . 'password_policy_role.php';
include 'drupal7.1' . DIRECTORY_SEPARATOR . 'role.php';
include 'drupal7.1' . DIRECTORY_SEPARATOR . 'role_permission.php';
include 'drupal7.1' . DIRECTORY_SEPARATOR . 'system.php';
+114 −0
Changes for tests/fixtures/drupal7.1/password_policy.php: 114 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php
// phpcs:ignoreFile
/**
 * @file
 * A database agnostic dump for testing purposes.
 *
 * This file was generated by the Drupal 9.2.6 db-tools.php script.
 */

use Drupal\Core\Database\Database;

$connection = Database::getConnection();

$connection->schema()->createTable('password_policy', array(
  'fields' => array(
    'pid' => array(
      'type' => 'serial',
      'not null' => TRUE,
      'size' => 'normal',
    ),
    'name' => array(
      'type' => 'varchar',
      'not null' => TRUE,
      'length' => '64',
      'default' => '',
    ),
    'description' => array(
      'type' => 'varchar',
      'not null' => FALSE,
      'length' => '255',
      'default' => '',
    ),
    'enabled' => array(
      'type' => 'int',
      'not null' => TRUE,
      'size' => 'tiny',
      'default' => '0',
    ),
    'constraints' => array(
      'type' => 'varchar',
      'not null' => TRUE,
      'length' => '1024',
      'default' => '',
    ),
    'created' => array(
      'type' => 'int',
      'not null' => TRUE,
      'size' => 'normal',
      'default' => '0',
    ),
    'expiration' => array(
      'type' => 'int',
      'not null' => FALSE,
      'size' => 'normal',
      'default' => '0',
    ),
    'warning' => array(
      'type' => 'varchar',
      'not null' => FALSE,
      'length' => '64',
    ),
    'weight' => array(
      'type' => 'int',
      'not null' => TRUE,
      'size' => 'tiny',
      'default' => '0',
    ),
  ),
  'primary key' => array(
    'pid',
  ),
  'unique keys' => array(
    'name' => array(
      'name',
    ),
  ),
  'mysql_character_set' => 'utf8mb3',
));

$connection->insert('password_policy')
->fields(array(
  'pid',
  'name',
  'description',
  'enabled',
  'constraints',
  'created',
  'expiration',
  'warning',
  'weight',
))
->values(array(
  'pid' => '1',
  'name' => 'Password policy Example D7',
  'description' => '',
  'enabled' => '1',
  'constraints' => 'a:2:{s:9:"uppercase";s:1:"2";s:5:"digit";s:2:"10";}',
  'created' => '0',
  'expiration' => '10',
  'warning' => '5,9',
  'weight' => '0',
))
->values(array(
  'pid' => '2',
  'name' => 'Password policy Example 2  D7',
  'description' => '',
  'enabled' => '1',
  'constraints' => 'a:2:{s:9:"uppercase";s:1:"3";s:5:"digit";s:2:"11";}',
  'created' => '1651134689',
  'expiration' => '0',
  'warning' => '3',
  'weight' => '0',
))
->execute();
Loading