From 5e5738dcdddc3052e80504d45db657678cdaf6fa Mon Sep 17 00:00:00 2001
From: webchick <drupal@webchick.net>
Date: Mon, 19 Oct 2015 10:44:31 -0700
Subject: [PATCH] Issue #2560637 by phenaproxima, benjy, quietone, mikeryan,
 chx, neclimdul: Improve handling of uid 1 during migration

---
 .../Tests/Migrate/d7/MigrateCommentTest.php   |  7 --
 .../schema/migrate.data_types.schema.yml      |  7 ++
 .../migrate/destination/EntityContentBase.php | 11 +++
 core/modules/migrate/src/Row.php              |  9 ++
 .../src/Tests/d6/EntityContentBaseTest.php    | 93 +++++++++++++++++++
 .../migrate_drupal/tests/fixtures/drupal7.php |  4 +-
 .../migrate_overwrite_test.info.yml           |  6 ++
 .../migration_templates/users.yml             | 31 +++++++
 .../src/Tests/Migrate/d7/MigrateNodeTest.php  |  2 +-
 .../src/Plugin/migrate/source/d6/User.php     |  2 +-
 .../src/Plugin/migrate/source/d7/User.php     |  2 +-
 11 files changed, 162 insertions(+), 12 deletions(-)
 create mode 100644 core/modules/migrate_drupal/src/Tests/d6/EntityContentBaseTest.php
 create mode 100644 core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migrate_overwrite_test.info.yml
 create mode 100644 core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migration_templates/users.yml

diff --git a/core/modules/comment/src/Tests/Migrate/d7/MigrateCommentTest.php b/core/modules/comment/src/Tests/Migrate/d7/MigrateCommentTest.php
index 6c7277e7e225..8d6e668df4bb 100644
--- a/core/modules/comment/src/Tests/Migrate/d7/MigrateCommentTest.php
+++ b/core/modules/comment/src/Tests/Migrate/d7/MigrateCommentTest.php
@@ -11,7 +11,6 @@
 use Drupal\comment\Entity\Comment;
 use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
 use Drupal\node\NodeInterface;
-use Drupal\user\Entity\User;
 
 /**
  * Tests migration of comments from Drupal 7.
@@ -37,12 +36,6 @@ protected function setUp() {
       'd7_user_role',
       'd7_user',
     ]);
-    // The test database doesn't include uid 1, so we'll need to create it.
-    User::create(array(
-      'uid' => 1,
-      'name' => 'admin',
-      'mail' => 'admin@local.host',
-    ))->save();
     $this->executeMigration('d7_node_type');
     // We only need the test_content_type node migration to run for real, so
     // mock all the others.
diff --git a/core/modules/migrate/config/schema/migrate.data_types.schema.yml b/core/modules/migrate/config/schema/migrate.data_types.schema.yml
index 7521ee129b09..60dd5193f66c 100644
--- a/core/modules/migrate/config/schema/migrate.data_types.schema.yml
+++ b/core/modules/migrate/config/schema/migrate.data_types.schema.yml
@@ -10,6 +10,13 @@ migrate_plugin:
 migrate_destination:
   type: migrate_plugin
   label: 'Destination'
+  mapping:
+    overwrite_properties:
+      type: sequence
+      label: 'Properties to overwrite'
+      sequence:
+        type: string
+        label: 'Property'
 
 migrate_source:
   type: migrate_plugin
diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php
index 69c9e660a1fd..f80034ce2c26 100644
--- a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php
+++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php
@@ -109,6 +109,17 @@ public function getIds() {
    *   The row object to update from.
    */
   protected function updateEntity(EntityInterface $entity, Row $row) {
+    // If the migration has specified a list of properties to be overwritten,
+    // clone the row with an empty set of destination values, and re-add only
+    // the specified properties.
+    if (isset($this->configuration['overwrite_properties'])) {
+      $clone = $row->cloneWithoutDestination();
+      foreach ($this->configuration['overwrite_properties'] as $property) {
+        $clone->setDestinationProperty($property, $row->getDestinationProperty($property));
+      }
+      $row = $clone;
+    }
+
     foreach ($row->getDestination() as $field_name => $values) {
       $field = $entity->$field_name;
       if ($field instanceof TypedDataInterface) {
diff --git a/core/modules/migrate/src/Row.php b/core/modules/migrate/src/Row.php
index 8774976950fa..e055c1d4c4dc 100644
--- a/core/modules/migrate/src/Row.php
+++ b/core/modules/migrate/src/Row.php
@@ -187,6 +187,15 @@ public function freezeSource() {
     return $this;
   }
 
+  /**
+   * Clones the row with an empty set of destination values.
+   *
+   * @return static
+   */
+  public function cloneWithoutDestination() {
+    return (new static($this->getSource(), $this->sourceIds, $this->isStub()))->freezeSource();
+  }
+
   /**
    * Tests if destination property exists.
    *
diff --git a/core/modules/migrate_drupal/src/Tests/d6/EntityContentBaseTest.php b/core/modules/migrate_drupal/src/Tests/d6/EntityContentBaseTest.php
new file mode 100644
index 000000000000..1015aa16ad4b
--- /dev/null
+++ b/core/modules/migrate_drupal/src/Tests/d6/EntityContentBaseTest.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Tests\d6\EntityContentBaseTest.
+ */
+
+namespace Drupal\migrate_drupal\Tests\d6;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\user\Entity\User;
+
+/**
+ * @group migrate_drupal
+ */
+class EntityContentBaseTest extends MigrateDrupal6TestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['migrate_overwrite_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Create a field on the user entity so that we can test nested property
+    // overwrites.
+    // @see static::testOverwriteSelectedNestedProperty()
+    FieldStorageConfig::create([
+      'field_name' => 'signature',
+      'entity_type' => 'user',
+      'type' => 'text_long',
+    ])->save();
+
+    FieldConfig::create([
+      'field_name' => 'signature',
+      'entity_type' => 'user',
+      'bundle' => 'user',
+    ])->save();
+
+    User::create([
+      'uid' => 2,
+      'name' => 'Ford Prefect',
+      'mail' => 'ford.prefect@localhost',
+      'signature' => array(
+        array(
+          'value' => 'Bring a towel.',
+          'format' => 'filtered_html',
+        ),
+      ),
+      'init' => 'proto@zo.an',
+    ])->save();
+
+    $this->executeMigrations(['d6_filter_format', 'd6_user_role']);
+  }
+
+  /**
+   * Tests overwriting all mapped properties in the destination entity (default
+   * behavior).
+   */
+  public function testOverwriteAllMappedProperties() {
+    $this->executeMigration('d6_user');
+    /** @var \Drupal\user\UserInterface $account */
+    $account = User::load(2);
+    $this->assertIdentical('john.doe', $account->label());
+    $this->assertIdentical('john.doe@example.com', $account->getEmail());
+    $this->assertIdentical('doe@example.com', $account->getInitialEmail());
+  }
+
+  /**
+   * Tests overwriting selected properties in the destination entity, specified
+   * in the destination configuration.
+   */
+  public function testOverwriteProperties() {
+    // Execute the migration in migrate_overwrite_test, which documents how
+    // property overwrites work.
+    $this->executeMigration('users');
+
+    /** @var \Drupal\user\UserInterface $account */
+    $account = User::load(2);
+    $this->assertIdentical('john.doe', $account->label());
+    $this->assertIdentical('john.doe@example.com', $account->getEmail());
+    $this->assertIdentical('The answer is 42.', $account->signature->value);
+    // This value is not overwritten because it's not listed in
+    // overwrite_properties.
+    $this->assertIdentical('proto@zo.an', $account->getInitialEmail());
+  }
+
+}
diff --git a/core/modules/migrate_drupal/tests/fixtures/drupal7.php b/core/modules/migrate_drupal/tests/fixtures/drupal7.php
index 3a9d266cf160..0804334f9c4a 100644
--- a/core/modules/migrate_drupal/tests/fixtures/drupal7.php
+++ b/core/modules/migrate_drupal/tests/fixtures/drupal7.php
@@ -39895,9 +39895,9 @@
 ))
 ->values(array(
   'uid' => '1',
-  'name' => 'root',
+  'name' => 'admin',
   'pass' => '$S$D/HVkgCg1Hvi7DN5KVSgNl.2C5g8W6oe/OoIRMUlyjkmPugQRhoB',
-  'mail' => '',
+  'mail' => 'admin@local.host',
   'theme' => '',
   'signature' => '',
   'signature_format' => NULL,
diff --git a/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migrate_overwrite_test.info.yml b/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migrate_overwrite_test.info.yml
new file mode 100644
index 000000000000..503c6cec716d
--- /dev/null
+++ b/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migrate_overwrite_test.info.yml
@@ -0,0 +1,6 @@
+name: 'Migrate property overwrite test'
+type: module
+description: 'Example module demonstrating property overwrite support in the Migrate API.'
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migration_templates/users.yml b/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migration_templates/users.yml
new file mode 100644
index 000000000000..e23d90d9d7c2
--- /dev/null
+++ b/core/modules/migrate_drupal/tests/modules/migrate_overwrite_test/migration_templates/users.yml
@@ -0,0 +1,31 @@
+id: users
+label: User migration
+migration_tags:
+  - Drupal 6
+  - Drupal 7
+source:
+  plugin: d6_user
+process:
+  # If the entity's ID is migrated, the Migrate API will try to update
+  # an existing entity with that ID. If no entity with that ID already
+  # exists, it will be created.
+  uid: uid
+  name: name
+  mail: mail
+  password: password
+  'signature/value':
+    plugin: default_value
+    default_value: 'The answer is 42.'
+destination:
+  plugin: entity:user
+  # If the destination is going to update an existing user, you can optionally
+  # specify the properties that should be overwritten. For example, if the
+  # migration tries to import user 31 and user 31 already exists in the
+  # destination database, only the 'name' and 'mail' properties of the user
+  # will be overwritten. If user 31 doesn't exist, it will be created and
+  # the overwrite_properties list will be ignored.
+  overwrite_properties:
+    - name
+    - mail
+    # It's possible to overwrite nested properties too.
+    - 'signature/value'
diff --git a/core/modules/node/src/Tests/Migrate/d7/MigrateNodeTest.php b/core/modules/node/src/Tests/Migrate/d7/MigrateNodeTest.php
index b2537f573bfe..9a9024babece 100644
--- a/core/modules/node/src/Tests/Migrate/d7/MigrateNodeTest.php
+++ b/core/modules/node/src/Tests/Migrate/d7/MigrateNodeTest.php
@@ -123,7 +123,7 @@ protected function assertRevision($id, $title, $uid, $log, $timestamp) {
    */
   public function testNode() {
     $this->assertEntity(1, 'test_content_type', 'en', 'A Node', '2', TRUE, '1421727515', '1441032132', TRUE, FALSE);
-    $this->assertRevision(1, 'A Node', '2', NULL, '1441032132');
+    $this->assertRevision(1, 'A Node', '1', NULL, '1441032132');
 
     $node = Node::load(1);
     $this->assertTrue($node->field_boolean->value);
diff --git a/core/modules/user/src/Plugin/migrate/source/d6/User.php b/core/modules/user/src/Plugin/migrate/source/d6/User.php
index 59c3229ab1ba..e344d06517ad 100644
--- a/core/modules/user/src/Plugin/migrate/source/d6/User.php
+++ b/core/modules/user/src/Plugin/migrate/source/d6/User.php
@@ -25,7 +25,7 @@ class User extends DrupalSqlBase {
   public function query() {
     return $this->select('users', 'u')
       ->fields('u', array_keys($this->baseFields()))
-      ->condition('uid', 1, '>');
+      ->condition('uid', 0, '>');
   }
 
   /**
diff --git a/core/modules/user/src/Plugin/migrate/source/d7/User.php b/core/modules/user/src/Plugin/migrate/source/d7/User.php
index fc05bf3cd911..fa872c63874e 100755
--- a/core/modules/user/src/Plugin/migrate/source/d7/User.php
+++ b/core/modules/user/src/Plugin/migrate/source/d7/User.php
@@ -25,7 +25,7 @@ class User extends FieldableEntity {
   public function query() {
     return $this->select('users', 'u')
       ->fields('u')
-      ->condition('uid', 1, '>');
+      ->condition('uid', 0, '>');
   }
 
   /**
-- 
GitLab