Problem/Motivation

I needed to map a location field to an address field during a d7-to-d8 node migration.

Proposed resolution

A simple process plugin did the trick. I will attach it below.

Remaining tasks

Could use tests.

Comments

ronaldmulero created an issue. See original summary.

ronaldmulero’s picture

Status: Active » Needs review
StatusFileSize
new2.52 KB

Here's the patch.

Status: Needs review » Needs work

The last submitted patch, 2: Add-Location-to-Address-2974631-2.patch, failed testing. View results

ronaldmulero’s picture

Status: Needs work » Needs review
StatusFileSize
new2.52 KB
new925 bytes

Curse you missing semicolon!

roam2345’s picture

Status: Needs review » Needs work

Looks like this code assumes the database connection is named 'migrate' for the migration source.

Drupal\Core\Database\ConnectionNotDefinedException: The specified database connection is not defined: migrate in /app/docroot/core/lib/Drupal/Core/Database/Database.php:361                                                                         [error]
Stack trace:
#0 /app/docroot/core/lib/Drupal/Core/Database/Database.php(166): Drupal\Core\Database\Database::openConnection('migrate', 'default')
#1 /app/docroot/modules/contrib/address/src/Plugin/migrate/process/LocationToAddress.php(34): Drupal\Core\Database\Database::getConnection('default', 'migrate')
#2 /app/docroot/core/modules/migrate/src/MigrateExecutable.php(368): Drupal\address\Plugin\migrate\process\LocationToAddress->transform(Array, Object(Drupal\migrate_tools\MigrateExecutable), Object(Drupal\migrate\Row), 'field_address')
#3 /app/docroot/core/modules/migrate/src/MigrateExecutable.php(203): Drupal\migrate\MigrateExecutable->processRow(Object(Drupal\migrate\Row))
#4 /var/www/.composer/vendor/drush/drush/includes/drush.inc(721): Drupal\migrate\MigrateExecutable->import()
#5 /var/www/.composer/vendor/drush/drush/includes/drush.inc(712): drush_call_user_func_array(Array, Array)
#6 /app/docroot/modules/contrib/migrate_tools/migrate_tools.drush.inc(303): drush_op(Array)
#7 [internal function]: _drush_migrate_tools_execute_migration(Object(Drupal\migrate\Plugin\Migration), 'upgrade_d7_node...', Array)
#8 /app/docroot/modules/contrib/migrate_tools/migrate_tools.drush.inc(267): array_walk(Array, '_drush_migrate_...', Array)
#9 /var/www/.composer/vendor/drush/drush/includes/command.inc(422): drush_migrate_tools_migrate_import('upgrade_d7_node...')
#10 /var/www/.composer/vendor/drush/drush/includes/command.inc(231): _drush_invoke_hooks(Array, Array)
#11 /var/www/.composer/vendor/drush/drush/includes/command.inc(199): drush_command('upgrade_d7_node...')
#12 /var/www/.composer/vendor/drush/drush/lib/Drush/Boot/BaseBoot.php(67): drush_dispatch(Array)
#13 /var/www/.composer/vendor/drush/drush/includes/preflight.inc(66): Drush\Boot\BaseBoot->bootstrap_and_dispatch()
#14 /var/www/.composer/vendor/drush/drush/drush.php(12): drush_main()
#15 {main}

damienmckenna’s picture

Title: Add Location-to-Address migrate process plugin » Add D7 Location-to-Address migrate process plugin
damienmckenna’s picture

Status: Needs work » Needs review
StatusFileSize
new3.07 KB
new2.02 KB

Minor changes.

damienmckenna’s picture

Status: Needs review » Needs work

This can help build out a full migrate plugin, but it needs a lot of work as-is.

papagrande’s picture

It looks like it also needs a D7 source plugin too as the location 'lid' comes from the non-standard 'location_instance' table.

pieterdc’s picture

Thanks for sharing but the patch is unusable in its current state.

Because even if you correct the database connection to your use case, the location id is not fetched, unless you have custom code that's not in this patch. If you do, please share.

As @PapaGrande already stated, it needs a migration source plugin to fetch the correct data.
Documentation: https://www.drupal.org/docs/8/api/migrate-api/migrate-source-plugins/ove...
AFAIK, source plugins can't be set on field, but only on migration level. So, that'd result in a separate migration for the locations requiring the related node migration to have run before it.

Unless that's all not needed with a field migration along the lines of this documentation: https://www.drupal.org/docs/8/api/migrate-api/writing-migrations-for-con...

bob.hinrichs’s picture

Following .. And kind of amazed this is all the activity we have on this issue when 46,000 sites use the location module, and this work was begun over a year ago and no resolution is mentioned. I will need to somehow migrate location data myself so am interested in how people have resolved this?

papagrande’s picture

@bob.hinrichs, my workaround was to have the client manually copy the 79 addresses to new paragraph fields in D7. Then it was easy to migrate the paragraphs. Obviously not a viable solution for anyone with more than a few location instances, but perhaps you could do something in bulk with SQL at the database level.

bob.hinrichs’s picture

This is doable using FieldPluginBase to cause the field to automatically get created during migration. Use AddressField.php in the Address module as your example. The module has an example of migrating other modules' address types into the Address module type. Creating a new field on a destination to hold migrated data is not well-documented but this seems the most straightforward example.

In your custom migration code for your node type, extend the Node migration and override prepareRow(). Add the query in this method to grab the location data on D7.

In the prepareRow() code, set source property for the D7 location data into some custom properties that can be used to pull in the data.
In this example, the keys 'country_code', 'city', 'state', etc. that you see below.

In your node migration yml process block, include these lines:
'field_address/country_code': country_code
'field_address/address_line1': street_address
'field_address/locality': city
'field_address/province': state
'field_address/administrative_area': state
'field_address/postal_code': zip

bogdog400’s picture

Thx for the basic sketch on how to accomplish this. If anyone can post working code, it would be quite a gift. I'm just starting to try to understand migration templates and functions.

cmah’s picture

@bob.hinrichs, you used FieldPluginBase and extended the Node migration in a custom source plugin?

I was under the impression that the recommended approach was to create a new process plugin rather than a source plugin, extending ProcessPluginBase and putting the query inside the transform() method. I did this to convert Location to Address (and then a second separate process plugin to convert Location to Geofield for the latitude and longitude). If I've done this completely incorrectly, please explain why a source plugin is better -- I am new to this and it is all very overwhelming!

poker.ca’s picture

@cmah: if the method in #15 worked, could you post a patch with that code? Sounds like exactly what I need!

cmah’s picture

Hi @poker.ca, I don't know how to contribute patches to an existing drupal contrib module. All I know how to do is write my own custom modules. However, if it helps you, the code I wrote for the address portion of my custom process plugin was basically like this (which I'm sure I just copied out of @ronaldmulero's patch):

<?php

namespace Drupal\my_custom_module_migrate\Plugin\migrate\process;

use Drupal\Core\Database\Database;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;

/**
 * Maps D7 location values to D8 address values.
 *
 * Example:
 *
 * @code
 * process:
 *   field_address:
 *     plugin: my_location_to_address
 *     source: field_location
 * @endcode
 *
 * @MigrateProcessPlugin(
 *   id = "my_location_to_address"
 * )
 */
class LocationToAddress extends ProcessPluginBase {

  /**
   * {@inheritdoc}
   */
  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
    // $value is the array containing the lid for this location.
    $lid = $value['lid'];

    // Connect to the database defined by key 'migrate_source_db' (it would be
    // better to work off the configured connection, but I'm not sure how 
    // to do that):
    $db = Database::getConnection('default', 'migration_source_db');
    $location = $db->select('location', 'l')
      ->where('l.lid = ' . $lid)
      ->fields('l', [
        'name',
        'street',
        'additional',
        'city',
        'province',
        'postal_code',
        'country',
      ])
      ->execute()
      ->fetchAssoc();
    $address = [
      'given_name' => '',
      'additional_name' => '',
      'family_name' => '',
      'organization' => $location['name'],
      'address_line1' => $location['street'],
      'address_line2' => $location['additional'],
      'postal_code' => $location['postal_code'],
      'sorting_code' => '',
      'dependent_locality' => '',
      'locality' => $location['city'],
      'administrative_area' => $location['province'],
      'country_code' => strtoupper($location['country']),
    ];
    return $address;
  }

}

If you also need the Latitude and Longitude in your D8 site and not just the address, make a separate new Geofield for that, and then just make a separate plugin for that and grab only the latitude and longitude.

So after you write those two process plugins you then just do this in your YML:

  field_my_new_address_field:
    -
      plugin: my_location_to_address
      source: field_my_old_location_field
  field_my_new_geo_location_field:
    -
      plugin: my_location_to_geofield
      source: field_my_old_location_field
    -
      plugin: geofield_latlon

Again, I can't really vouch for this code, but maybe it will help you anyway. It worked for me -- it's basically the exact same code as what @ronaldmulero already posted, so I'm pretty confused as to why we would need a source plugin instead (or, in addition, perhaps -- that's the part I'm not clear on) -- several people who know a lot more about this stuff than I do have said that this is the case, so I do believe them, but I also know that this code worked for me.

ccjjmartin’s picture

StatusFileSize
new2.66 KB

Just to show another perspective, I have attached a source plugin as opposed to a process plugin. I used it by creating a content type with a field that had machine name "field_address" then added the following migration config, replace CONTENT_TYPE_HERE with your content type:

langcode: en
status: true
dependencies: {  }
id: location_to_address_CONTENT_TYPE_HERE
class: Drupal\migrate\Plugin\Migration
field_plugin_method: null
cck_plugin_method: null
migration_tags:
  - 'Drupal 7'
  - Content
migration_group: locations
label: 'Locations to Addresses - CONTENT_TYPE_HERE'
source:
  plugin: d7_location
process:
  nid: nid
  vid: vid
  title: title
  field_address/country_code:
    plugin: default_value
    default_value: US
  field_address/langcode:
    plugin: default_value
    default_value: en
  field_address/organization: name
  field_address/address_line1: street
  field_address/address_line2: additional
  field_address/locality: city
  field_address/administrative_area: province
  field_address/postal_code: postal_code
destination:
  plugin: 'entity:node'
  provider: migrate
  default_bundle: CONTENT_TYPE_HERE
  overwrite_properties:
    - field_address
migration_dependencies: null

Seems to work for me. I will say that all of my content only has 1 location associated with it so multiple locations will probably break the migration code but the source plugin code should still return all of the data appropriately. Since the migration code isn't in the patch it shouldn't be an issue.

ccjjmartin’s picture

Status: Needs work » Needs review
ccjjmartin’s picture

I forgot to mention that this requires another migration to run the initial import of the content type and this updates the existing content's address field. Specifically it uses the overwrite_properties option here: https://api.drupal.org/api/drupal/core%21modules%21migrate%21src%21Plugi...

damienmckenna’s picture

While this plugin will be great on its own, I opened a separate issue to tie it into a full field migration: #3173870: Support core migrations for automated update from Location (D7) fields

damienmckenna’s picture

I also created a separate issue to handle Location Node, which doesn't use Field API: #3173871: Support core migrations for automated update from Location Node (D7) data

huzooka’s picture

I think that #3173870: Support core migrations for automated update from Location (D7) fields is already good enough to create test coverage for it.

ccjjmartin’s picture

StatusFileSize
new2.79 KB

So for the address field DamienMcKenna's solution seems like a good approach, I haven't tested it but in theory it is better than the approach I am using. The approach I am using technically supports GeoFields too so I am going to upload a new version that I tweaked to better support node revisions and publishing status.

If anyone is interested in what the migration looks like for integration with the Group module, it looks like this:

langcode: en
status: true
dependencies: {  }
id: group_location_to_address_GROUP_BUNDLE
class: Drupal\migrate\Plugin\Migration
field_plugin_method: null
cck_plugin_method: null
migration_tags:
  - 'Drupal 7'
  - Content
migration_group: locations
label: 'Locations to Addresses'
source:
  plugin: d7_location
process:
  id: nid
  label: title
  uid: uid
  filter_type:
    -
      plugin: skip_on_value
      method: row
      not_equals: true
      value:
        - CONTENT_TYPE
      source: type
  filter_status:
    -
      plugin: skip_on_value
      method: row
      value: FALSE
      source: status
  field_address/country_code:
    plugin: default_value
    default_value: US
  field_address/langcode:
    plugin: default_value
    default_value: en
  field_address/organization: name
  field_address/address_line1: street
  field_address/address_line2: additional
  field_address/locality: city
  field_address/administrative_area: province
  field_address/postal_code: postal_code
destination:
  plugin: 'entity:group'
  provider: migrate
  default_bundle: GROUP_BUNDLE
  overwrite_properties:
    - field_address
migration_dependencies: null

The field on the group entity has a machine name of "field_address" and it overwrites the existing Group imported in a separate migration. It took me a while to debug that the id should be called "id" and not "nid" or "gid".

iancawthorne’s picture

Has anyone tried this module: https://www.drupal.org/project/location_migration
To see if it does what is set out here?

mparker17’s picture

StatusFileSize
new3.74 KB
new523 bytes

Now that #3189605: Declare upgrade status from D6, D7 has been committed; here's the patch from #24 with the upgrade status set properly.

wim leers’s picture

We’ve worked hard to stabilize https://www.drupal.org/project/location_migration.

It’s been working successfully for many customers for a year now.

damienmckenna’s picture

+1 for location_migration.

bojanz’s picture

Category: Feature request » Task
Status: Needs review » Fixed

Awesome, since we now have a tested alternative at https://www.drupal.org/project/location_migration, I am closing this issue as done and giving everyone issue credit.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.