To migrate from Location 6.x-3.x to Addressfield 7.x-1.x in Drupal 7 you must create a custom class since the location module data is not stored in CCK. The location and location_instance tables must be manually added by implementing the query method. As of Addressfield 7.x-1.x and Migrate 7.x-2.5 the address subfields (e.g., "field_address:administrative_area") are not available so the location columns must be mapped as arguments to the address field country mapping, and then hidden.


/**
 * Migrate the venue content type.
 */
class VenueMigration extends DrupalNode6Migration {
  public function __construct(array $arguments) {
    parent::__construct($arguments);

    // Map address field
    $address_arguments = array(
      'administrative_area' => array('source_field' => 'province'),
      'locality' => array('source_field' => 'city'),
      'thoroughfare' => array('source_field' => 'street'),
      'premise' => array('source_field' => 'additional'),
      'postal_code' => array('source_field' => 'postal_code'),
    );
    $this->addFieldMapping('field_address', 'country')
         ->arguments($address_arguments);

    // Hide these sources because they are added as arguments
    $unmapped_sources = array(
      'province',
      'city',
      'street',
      'additional',
      'postal_code',
    );
    $this->addUnmigratedSources($unmapped_sources, t('DNM'));
  }

  protected function query() {
    $query = parent::query();

    // Join location table, which is not CCK
    $query->join('location_instance', 'i', 'i.nid = n.nid AND i.vid = n.vid');
    $query->join('location', 'l', 'l.lid = i.lid');
    $query->fields('l', array('street', 'additional', 'city', 'province', 'postal_code', 'country'));

    return $query;
  }

}

Comments

adrupaler’s picture

I am going to do a D7-D7 migrate for a field name but nothing was transferred using $this->addFieldMapping();

<?php
    parent::__construct($arguments);
    $this->addFieldMapping('field_fullname:title', 'field_fullname_title');
    $this->addFieldMapping('field_fullname:given', 'field_fullname_given');
    $this->addFieldMapping('field_fullname:middle', 'field_fullname_middle');
    $this->addFieldMapping('field_fullname:family', 'field_fullname_family');
    $this->addFieldMapping('field_fullname:generational', 'field_fullname_generational');
    $this->addFieldMapping('field_fullname:credentials', 'field_fullname_credentials');
?>

since the source and destination has same name , should I use smething like the above example?

Bezude’s picture

This code was not quite enough to get the import working for me. It shuttles all the data off in the arguments array, but says nothing about how to handle that data now that it's there.

A field handler is what's needed. The migrate_extras module already has an implementation for Addressfield so it was just a matter of enabling migrate_extras and adding the proper field handler to hook_migrate_api like so:

function myImport_migrate_api() {
  $api = array(
    'api' => 2,
    'field handlers' => array('MigrateAddressFieldHandler'),
  );
  return $api;
}
robcarr’s picture

In the latest version of migrate/d2d (2.1+), using the method described results in the following error:

The field mapping arguments() method is now deprecated - please use subfield notation instead.

Subfields for Addressfield can be mapped in a fairly simple way without the need for the Migrate Extras module, for example:

$this->addFieldMapping('field_location:locality', 'city')
     ->sourceMigration('MyMigration');

However, there is no subfield for the Country (any ideas?)

mieg’s picture

I didn't get this to work for countries, and i found out it was because in D6 country codes are lowercase, and in D7 they are uppercase. This is my working code:

$this->addFieldMapping('field_address', 'country')->callbacks('strtoupper');

$this->addFieldMapping('field_address:administrative_area', 'province');
$this->addFieldMapping('field_address:locality', 'city');
$this->addFieldMapping('field_address:thoroughfare', 'street');
$this->addFieldMapping('field_address:premise', 'additional');
$this->addFieldMapping('field_address:postal_code', 'postal_code');

davemybes’s picture

To do a straight migration, simply add the following protected function into your migration class (I've included name, lat, long and source fields too - is_primary is usually not needed, but can be added the end of the fields array if desired):

  protected function query() {
    $query = parent::query();
    // Join location table, which is not CCK
    $query->join('location_instance', 'i', 'i.nid = n.nid AND i.vid = n.vid');
    $query->join('location', 'l', 'l.lid = i.lid');
    $query->fields('l', array('name', 'street', 'additional', 'city', 'province', 'postal_code', 'country', 'latitude', 'longitude', 'source'));
    return $query;
  }

If you use migrate_d2d_ui (included from 2.1-beta1), you'll see the Location fields available for mapping the UI. To map fields manually in your migration class, you can use the main example above, but change the key values (the ones on the left) to match the names of the location fields e.g. 'administrative_area' => array('source_field' => 'province'), becomes 'province' => array('source_field' => 'province'),

______________________________________________________________________________________________________

mieg’s picture

at least use leftJoin here, because otherwise only nodes with a location will be migrated (and counted as source items!). Also, if you have multiple locations per node, you'll have do something else. See below for my code to deal with more than one location per node.

kscheirer’s picture

The location module includes a location.migrate.inc file. It does rely on migrate_extras being installed, but worked well for me with the migrate_d2d Wizard.

mieg’s picture

I try to migrate a D6 site to D7, and some nodes have multiple locations attached. This results in the query returning multiple results, and only the last result is migrated. How can i keep all locations?

mieg’s picture

I solved this by doing a query in preparerow. I tried to use grouping in the query but didn't succeed. This is not very efficient because it has to do a query for every row, but it works. Note how i included content_field_location because i needed to keep the order of locations (delta).

<?php
    public function prepareRow($row) {
        $fields = array('name','street', 'additional', 'city', 'province', 'postal_code', 'country','latitude','longitude','source');
        $query = Database::getConnection('default', $this->sourceConnection)
            ->select('location_instance', 'i')
            ->condition('i.nid',$row->nid)
            ->condition('i.vid',$row->vid);
        $query->join('location', 'l', 'l.lid = i.lid');
        $query->join('content_field_location', 'c', 'c.nid = i.nid AND c.field_location_lid = i.lid AND c.vid = i.vid');
        $query->orderBy('c.delta','ASC');
        $query->fields('l', $fields);
        $result = $query->execute();
        foreach($fields as $field) {
            $fullfield = 'locations:'.$field;
            $row->$fullfield = array();
        }
        foreach ($result as $location) {
            if ($location->country) {
                $row->locations[] = strtoupper($location->country);
                foreach ($fields as $field) {
                    $fullfield = 'locations:' . $field;
                    array_push($row->$fullfield, $location->$field);
                }
            }
        }
    }
?>

I later ran into a problem where some UK addresses had the wrong country code (UK) from the D6 location module. I manually did a query in the database to change this to GB, but if i had known this beforehand i would have put a statement near the strtoupper to do it here...

Summit’s picture

Hi,
Anyone has the full working code for this?
I am working with weblinks contenttype with Location in D6, and want to be able to use also Addressfield in D7.
Thanks a lot in advance,
greetings, Martijn