Commerce Migrate Übercart-module

Uit De Vliegende Brigade
Ga naar: navigatie, zoeken
Statusscherm na een succesvolle migratie

De Commerce Migrate Übercart-module importeert Übercartobjecten in een Drupal Commerce-site. Dit is de meestgenoemde oplossing voor migratie van Übercart naar Drupal Commerce.

Installatie

De module-home page en het readme-bestand van deze module, doen alsof installatie heel ingewikkeld is. Dat is het niet. Dit werkt namelijk perfect en is zo simpel als wat:

drush en -y migrate
drush en -y migrate_ui
drush en -y migrate_d2d
drush en -y migrate_d2d_ui
drush en -y migrate_extras
drush en -y commerce_migrate
drush cc all
drush en -y commerce_migrate_ubercart

Bij twijfel kun je in de Migrate-webinterface nog een keer op reregister modules klikken, en nog een keer de cache legen, maar dan werkt het ook echt allemaal.

Wat wel belangrijk is:

  • Producten kunnen alleen gemigreerd worden naar een producttype met de standaardnaam (ben ik effe kwijt). Dit producttype kun je wel aanpassen qua velden e.d.
  • Productnodes kunnen alleen gemigreerd worden naar een bundle met de standaardnaam product_display

Video Randy Fay

Still video Randy Fay: Profielen zijn direct beschikbaar, dus ook zonder import van orders. User is Anonymous, wellicht omdat UID's niet gemapped hoeften te worden

Doorgaans heb ik geen geduld voor video's. Nu wel, omdat ik geen betere ideeën meer heb. De video bevalt me erg goed. Maar let op: Het komt uit 2011.

Uitgangspunt

  • Bron: Übercart-site op Drupal 7. Geen attributen, geen taxonomieën
  • Doel: Verse Drupal Commerce Kickstarter-site. Helemaal vers is-ie trouwens niet, want settings.php was al voorzien van een tweede database-connectiestring, en er was een custom VBO-view voor ProductTypes.

Acties

  • Alle bestaande productypes verwijderen: Producttypes worden gedurende de migratie aangemaakt. Alle productklasses van de Übercart-sites, worden gemigreerd als ProductTypes in Drupal Commerce
  • Activeer relevante modules:
    • Commerce Migrate
    • Commerce Migrate Übercart
    • Migrate UI
  • Content » Migrate » Commerce Migrate Ubercart Options: Additionele database specificeren, UID niet mappen (hij legt uit waarom), plus pad specificeren naar locale instantie
  • Start taak CommerceMigrateUbercartProductType. Er wordt niets geconfigureerd!
  • Vervolgens komen additionele migratie-taken beschikbaar in het overzicht
  • Taak CommerceMigrateUbercartCustomerBillingProfile, weer zonder te programmeren. Resultaten zijn direct te zijn
  • Bekijk gemigreerde ProductTypes. Afbeeldingen zijn met de ProductTypes meegenomen
  • Taak CommerceMigrateUbercartNodeProduct
  • Migreer orders
  • Migreer line items

...Klaar.

Volgorde van migraties

Migraties van de verschillende onderdelen, moet in een bepaalde volgorde, ivm. onderlingen afhankelijkheden:

Producten                                            → Productnodes
{Producten, Productnodes, Klanten, Billing profiles} → Orders
Orders                                               → Line items?

Concreet:

  1. Rollen, Klanten, Taxonomieën (mbv. migrate_d2d)
  2. ProductTypes incl. productafbeeldingen
  3. ProductNodes incl. productafbeeldingen
  4. Billing profiles
  5. Orders
  6. Line items
  7. Shipping line items
  8. Transactions.

CommerceMigrateUbercartProductProduct

ProductType product is met allerlei maatwerkvelden uitgebreid. Het statement MigrateDestinationEntityAPI::getKeySchema('commerce_product'), in onderdeel MapClass van de betreffende migratietaak, pikt die nieuwe velden feilloos op, inclusief afbeeldingsvelden. Merk verder op dat er handmatig een veld voor path was toegevoegd, maar dat dat redundant blijkt te zijn

Taak CommerceMigrateUbercartProductProduct migreert ProductTypes. In weerwil van de uitgebreide informatie hieronder, kun je ProductTypes prima migreren zonder iets aan een PHP-bestand te veranderen. Je hoeft alleen de gevraagde ProductTypes te defineren + reregister, en je kunt al aan de slag.

Vereisten

Homepage CMU-module:

* The migration will ask you to create any missing product_types in the D7 system.
* Each product type gets its own migration. Duplicate SKUs will get _2 and _3 (automatic de-duping).
* Each product type gets its own node migration, which creates the matching 

README.txt:

* You must have a product_type for each type that was defined in the old site,
  the module will prompt you to do so if it has not been done.
  • Zolang de gevraagde ProductTypes ontbreken, is deze migratietaak niet beschikbaar
  • Na aanmaak van de betreffende ProductType(s), moet je reregister activeren.

Bron

In de meegeleverde query worden middels een join tussen de tabellen node en uc_products, de volgende 10 velden ontsloten:

SELECT DISTINCT 

   n.nid AS nid, 
   n.vid AS vid, 
   n.type AS type, 
   n.title AS title, 
   n.created AS created, 
   n.changed AS changed, 
   n.status AS status, 
   ucp.model AS model, 
   ucp.sell_price AS sell_price,
   n.uid AS uid

FROM node n
INNER JOIN uc_products ucp 
ON n.nid = ucp.nid AND n.vid = ucp.vid
WHERE  (n.type = 'product') 

Voor een migratie in april 2016 heb ik de bronquery als volgt uitgebreid, waarbij sommige kolommen handmatig aan bepaalde tabellen waren toegevoegd:

SELECT DISTINCT 

	n.nid AS nid, 
	n.type AS type, 
	n.title AS title, 
	n.created AS created, 
	n.changed AS changed, 
	n.status AS status, 
	ucp.model AS model, 
	ucp.sell_price AS sell_price, 
	url_alias.dst AS dst, 
	node_revisions.teaser AS teaser, 
	node_revisions.body AS body, 
	files.filename as filename,
	files.filepath AS filepath, 
	n.uid AS uid

FROM node n
INNER JOIN uc_products ucp ON n.nid = ucp.nid AND n.vid = ucp.vid
INNER JOIN url_alias url_alias ON n.nid = url_alias.nid
INNER JOIN node_revisions node_revisions ON n.vid = node_revisions.vid
INNER JOIN content_field_image_cache content_field_image_cache ON content_field_image_cache.vid = n.vid
INNER JOIN files files ON files.fid = content_field_image_cache.field_image_cache_fid
WHERE  (n.type = 'product')

Doel

De volgende 13 doelvelden zijn standaard beschikbaar, waarvan er 12 zijn gemapped:

sku	                       The human readable product SKU.
type	                       The type of the product.
title	                       The title of the product.
language                       The language the product was created in.
status	                       Boolean indicating whether the product is active or disabled.
created	                       The date the product was created.
changed	                       The date the product was most recently updated.
uid	                       The unique ID of the product creator.
creator	                       The creator of the product.
commerce_price	               Field "commerce_price".
commerce_price:currency_code   Subfield: Currency code for the field
commerce_price:tax_rate	       Subfield: Tax rate for the field
path	                       Path alias

Velden die niet worden gemapped kom je ook weer tegen op het scherm Mapping: DNM:

  • creator
  • commerce_price:tax_rate.

Code

De gebruikte code voor de migratie in april 2016:

<?php

//
// Commerce Product migration.
//
// This is a dynamic migration, reused for every product type
// (so that products of each type can be imported separately)
//
// Improved beyond repair by Jeroen Strompf - April 2016

//////////////////////////////////////////////////////////////////////
// 0. Instantiate migration class
//////////////////////////////////////////////////////////////////////
//
class CommerceMigrateUbercartProductMigration extends Migration 
{

  public function __construct(array $arguments) 
  {
    parent::__construct($arguments);

    //////////////////////////////////////////////////////////////////////
    // 1. MapClass
    //////////////////////////////////////////////////////////////////////
    //
    // MapClass is concerned with instantiating a relation between source
    // and destination, for tracking individual migrations. Very handy
    // for rollbacks, and to keep referential integrity between items
    // before and after migration, even when the values of the actual
    // keys might have changed.
    //
    // MapClass basically consists of the following actions:
    //
    // * MigrateSQLMap: Define the compound primarey key for tracking 
    //   migrations, usually through an array, as is done here
    // * MigrateDestinationEntityAPI::getKeySchema: Obtain destination 
    //   object model
    // * Instantiate something
    
    // MigrateSQLMap
    /////////////////////
    // 
    $this->map = new MigrateSQLMap($this->machineName,
      array(
          'nid' => array(
            'type' => 'int',
            'unsigned' => TRUE,
            'not null' => TRUE,
            'description' => 'Ubercart node ID',
            'alias' => 'ucp',
          ),
      ),
      // MigrateDestinationEntityAPI::getKeySchema
      //////////////////////////////////////////////////
      // 
      // * getKeySchema only takes one argument. Original statement:
      //   MigrateDestinationEntityAPI::getKeySchema('commerce_product', $arguments['type'])
      // * The argument has to be 'commerce_product'. Names of actual 
      //   ProductTypes are not accepted
      //
      MigrateDestinationEntityAPI::getKeySchema('commerce_product')
    );

    // Instantiate destination ojbect
    ///////////////////////////////////////
    // 
    $this->destination = new MigrateDestinationEntityAPI('commerce_product', $arguments['type']);
    
    //////////////////////////////////////////////////////////////////////
    // 2. MigrateSource
    //////////////////////////////////////////////////////////////////////
    //
    // Basically: Build an SQL-query. You can see this query in the CMU-webinterface.
    // From there you can copy it to e.g. MySQL Workspace and execute it, to see if
    // it works
    
    // Initialise connection string
    /////////////////////////////////////
    //
    $connection = commerce_migrate_ubercart_get_source_connection();

    // Build query
    //////////////////
    //
    // * This query has been modified & extended
    // * An extra column 'nid' has been added to table 'url_alias' for easier joining
    // 
    //
    $query = $connection->select('node', 'n');
    $query->innerJoin('uc_products', 'ucp', 'n.nid = ucp.nid AND n.vid = ucp.vid');
    $query->innerJoin('url_alias', 'url_alias', 'n.nid = url_alias.nid');
    $query->innerJoin('node_revisions','node_revisions', 'n.vid = node_revisions.vid');
    $query->innerJoin('content_field_image_cache','content_field_image_cache', 'content_field_image_cache.vid = n.vid');
    $query->innerJoin('files','files','files.fid = content_field_image_cache.field_image_cache_fid');
    
    $query->fields('n', array('nid', 'type', 'title', 'created', 'changed', 'status'))
          ->fields('ucp', array('model', 'sell_price'))
          ->fields('url_alias',array('dst'))
          ->fields('node_revisions',array('teaser','body'))
          ->fields('files',array('filename'))      
          ->condition('n.type', $arguments['type'])
          ->distinct();

    // Extend query with user mapping, if so desired
    //
    if (variable_get('commerce_migrate_ubercart_user_map_ok', FALSE)) 
    {
      $query->addField('n', 'uid', 'uid');
    } 

    // Extend query with currency setting
    //
    $currency_code_setting = $connection->select('variable', 'v')
       ->fields('v', array('value'))
       ->condition('v.name', 'uc_currency_code')
       ->execute()->fetchField();

    // Instantiate source object
    //////////////////////////////////////
    //
    $this->source = new MigrateSourceSQL($query, array(), NULL, array('map_joinable' => FALSE));



    //////////////////////////////////////////////////////////////////////
    // 3. Field mapping
    //////////////////////////////////////////////////////////////////////

    // automaticstop: DNM
    //////////////////////////////////////////
    //
    $this->addUnmigratedDestinations(array('field_pt_automaticstop'));

    // body (various)
    /////////////////////////////////////////////
    //
    $this->addFieldMapping('field_pt_body', 'body');
    $this->addFieldMapping('field_pt_body:summary', 'teaser');
    $this->addFieldMapping('field_pt_body:format', NULL)->defaultValue('full_html');
    
    // changed & created
    /////////////////////////////////////////////
    //    
    $this->addFieldMapping('changed', 'changed');
    $this->addFieldMapping('created', 'created');

    // commerce_price (various)
    //////////////////////////////////////////
    //
     $this->addFieldMapping('commerce_price', 'sell_price');
    $this->addFieldMapping('commerce_price:tax_rate', NULL)
         ->defaultValue(.21);

    // 'Jeroen Strompf' → creator
    //////////////////////////////////////////
    //
    $this->addFieldMapping('creator',NULL)
         ->defaultValue('Jeroen Strompf');

    // currency
    //////////////////////////////////////////
    //
    // * Migrate currency denominator
    // * No currency denominator? → Use 'EUR'
    //
     if ($currency_code_setting == NULL) 
     {
      $this->addFieldMapping('commerce_price:currency_code', NULL)
           ->defaultValue('EUR');
    }
    else 
    {
      $this->addFieldMapping('commerce_price:currency_code', NULL)
           ->defaultValue(unserialize($currency_code_setting));
    }

    // dim1, dim2, dim3, dimNote: DNM
    //////////////////////////////////////////
    //
    $this->addUnmigratedDestinations(array('field_pt_dim1','field_pt_dim2','field_pt_dim3'));
    $this->addUnmigratedDestinations(array('field_pt_dimnote'));

    // 'nl' → language
    /////////////////////////////////////////////
    //
    $this->addFieldMapping('language', NULL)
         ->defaultValue('nl');

    // model → sku
    /////////////////////////////////////////////
    //
    $this->addFieldMapping('sku', 'model')
         ->dedupe('commerce_product', 'sku');

    // dst → path
    //////////////////////////////////////////
    //
    //
    $this->addFieldMapping('path', 'dst');

    // Productafbeelding
    //////////////////////////////////////////
    //
     $this->addFieldMapping('field_pt_productafbeelding','filename');
     $this->addFieldMapping('field_pt_productafbeelding:title','title');
     $this->addFieldMapping('field_pt_productafbeelding:alt','title');
     $this->addFieldMapping('field_pt_productafbeelding:file_class')
          ->defaultValue('MigrateFileUri'); // Value with apostrophes
     $this->addFieldMapping('field_pt_productafbeelding:file_replace')
          ->defaultValue(FILE_EXISTS_RENAME); // Value without apostrophes
     $this->addFieldMapping('field_pt_productafbeelding:preserve_files')
          ->defaultValue(TRUE); // Value without apostrophes
     $this->addFieldMapping('field_pt_productafbeelding:destination_dir')->defaultValue('public://');
     $this->addFieldMapping('field_pt_productafbeelding:destination_file')->issueGroup(t('DNM'));     
     $this->addFieldMapping('field_pt_productafbeelding:source_dir')->defaultValue('/var/www/kbo.dvb/sites/default/files');
     $this->addFieldMapping('field_pt_productafbeelding:urlencode')->defaultValue(TRUE);

    // 'Koolborstels 1.x' → productsoort
    //////////////////////////////////////////
    //
    $this->addFieldMapping('field_pt_productsoort', NULL)
         ->defaultValue('Koolborstels 1.x');

    // status → status
    /////////////////////////////////////////////
    //
    $this->addFieldMapping('status', 'status')
         ->defaultValue(1);

    // title → title
    /////////////////////////////////////////////
    //
    $this->addFieldMapping('title', 'title');
    
    // type → type
    /////////////////////////////////////////////
    //
    $this->addFieldMapping('type', 'type')
         ->defaultValue($arguments['type']);

    // uid
    /////////////////////////////////////////////
    //
    // Including mapping, if so required
    //
    if (variable_get('commerce_migrate_ubercart_user_map_ok', FALSE) && 
         variable_get('commerce_migrate_ubercart_user_migration_class', "") != "") 
    {
      $this->addFieldMapping('uid', 'uid')
           ->sourceMigration(variable_get('commerce_migrate_ubercart_user_migration_class', ""))
           ->defaultValue(0);
    }
    else 
    {
      $this->addFieldMapping('uid', 'uid')
           ->defaultValue(0);
    }

    // vid: Not being used at all anymore
    //////////////////////////////////////////
    //
    // $this->addFieldMapping(NULL, 'vid')->issueGroup(t('DNM'));
    
  } // public function __construct(array $arguments) 
  
} // class CommerceMigrateUbercartProductMigration extends Migration 

CommerceMigrateUbercartNodeProduct

Dit is wat je krijgt als je niets aanpast in product_node.inc: ProductNodes zonder productreferentie: Het proces weet niet welk veld hiervoor gebruikt moet worden
Het resultaat als aan alle details is gedacht, inclusief afbeelding, referentie en pad. Dit is het resultaat van de code van 11 april 2016, elders op deze pagina
  • De taak CommerceMigrateUbercartNodeProduct migreert ProductNodes oftewel ProductDisplays
  • Diverse CMU-taken kun je starten zonder enige aanpassing aan het bijbehorende .inc-bestand, maar dat is hier niet het geval: product_node.inc moet op z'n minst weten welk veld de productreferentie bevat naar het achterliggende ProductType.

Vereisten

CMU-home page:

Each product type gets its own node migration, which creates the matching 
product_display nodes. NIDs are preserved by default (duplication errors if you re-used nid 
5 for example). 

README.txt:

You must have an existing Product Display content type named product_display.

Achtergrond: Entiteiten Übercart database-niveau

Dit is hoe in Übercart op database-niveau afbeeldingen worden verzorgd.

Vanaf de nid van een Übercartproduct, heb je met twee joins alle relevante velden binnen handbereik.

Voorbeeldquery

SELECT DISTINCT 

   node.title AS title, 
   files.filename,
   files.filepath,
   files.filemime,
   content_field_image_cache.field_image_cache_data

FROM node
inner join content_field_image_cache on node.nid=content_field_image_cache.nid
inner join files on files.fid = content_field_image_cache.field_image_cache_fid

where node.nid=2;

Output

* title    = Koolborstelset 0100 voor Bosch handgereedschap
* filename = 16.jpg
* filepath = files//16.jpg
* filemime = image/jpb
* field_image_cache_data = 
   a:2:{s:3:"alt";s:38:"Afbeelding van ASEIN Koolborstels 0100";s:5:"title";s:23:"ASEIN Koolborstels 0100";}

Alleen sneu dat field_image_cache_data instellingen in een serialised formaat opslaat.

Achtergrond: Entiteiten Drupal Commerce Migrate-niveau

Zo'n huis-tuin-keuken productafbeelding bestaat uit maar liefst tien doelvelden:

* field_pn_afbeelding	                  Productafbeelding (image)
* field_pn_afbeelding:file_class	  Option: Implementation of MigrateFile to use
* field_pn_afbeelding:preserve_files	  Option: Boolean indicating whether files should 
                                          be preserved or deleted on rollback
* field_pn_afbeelding:destination_dir	  Subfield: Path within Drupal files directory to 
                                          store file
* field_pn_afbeelding:destination_file	  Subfield: Path within destination_dir to store 
                                          the file.
* field_pn_afbeelding:file_replace	  Option: Value of $replace in that file function. 
                                          Defaults to FILE_EXISTS_RENAME.
* field_pn_afbeelding:source_dir	  Subfield: Path to source file.
* field_pn_afbeelding:urlencode	          Option: Encode all segments of the incoming path 
                                          (defaults to TRUE).
* field_pn_afbeelding:alt	          Subfield: String to be used as the alt value
* field_pn_afbeelding:title	          Subfield: String to be used as the title value
De afbeelding-gerelateerde velden binnen CMU » CommerceMigrateUbercartNodeProduct » Destination

Wat de bron betreft: Dit is de query die CMU toont:

SELECT DISTINCT 

   n.nid AS nid, 
   n.type AS type, 
   n.title AS title, 
   n.created AS created, 
   n.changed AS changed, 
   nr.body AS body_value, 
   nr.teaser AS body_summary, 
   ff.name AS body_format, 
   n.uid AS uid

FROM node n
LEFT OUTER JOIN node_revisions nr ON n.nid = nr.nid AND n.vid = nr.vid
LEFT OUTER JOIN filter_formats ff ON nr.format = ff.format
WHERE  (n.type = 'Product') 

Gebruikte code - 11 April 2016

<?php

// Commerce Product Display Node migration.
//
// Aangepast voor migratie KBO 1.x naar KBO 2.x
// Jeroen Strompf - April 2016
//

//////////////////////////////////////////////////////////////////////
// 0. Instantiëer migratieklasse
//////////////////////////////////////////////////////////////////////
//
class CommerceMigrateUbercartNodeMigration extends Migration 
{

	public $filter_format_mapping = array();

	public function __construct(array $arguments) 
	{
		//////////////////////////////////////////////////////////////////////
		// 1. MapClass
		//////////////////////////////////////////////////////////////////////
		//
		parent::__construct($arguments);
    	$this->map = new MigrateSQLMap($this->machineName,
      	array(
        	'nid' => array(
          	'type' => 'int',
          	'unsigned' => TRUE,
          	'not null' => TRUE,
          	'description' => 'Ubercart node ID',
        		),
      	),
	
			// Doelobject: product_display
			//////////////////////////////////////////
			//
			// Het doelobject moet per se "product_display" heten
			//
      	MigrateDestinationNode::getKeySchema()
    	);

		//////////////////////////////////////////////////////////////////////
   	// 2. Bron
   	//////////////////////////////////////////////////////////////////////

    	$connection = commerce_migrate_ubercart_get_source_connection();

   	// Query
		//////////////////////////////////////////
		//
		$query = $connection->select('node', 'n');
		$query->leftJoin('node_revisions', 'nr', 'n.nid = nr.nid AND n.vid = nr.vid');
		$query->leftJoin('filter_formats', 'ff', 'nr.format = ff.format');
		$query->innerJoin('url_alias','url_alias','n.nid = url_alias.nid');
		$query->innerJoin('uc_products','uc_products','n.nid = uc_products.nid');
		$query->innerJoin('commerce_product','commerce_product','commerce_product.title = n.title');
		$query->innerJoin('content_field_image_cache','content_field_image_cache','n.nid=content_field_image_cache.nid');
		$query->innerJoin('files','files','files.fid = content_field_image_cache.field_image_cache_fid');
				
		$query->fields('n', array('nid', 'title', 'created', 'changed','status'));
		$query->addField('nr', 'body', 'body_value');
		$query->addField('nr', 'teaser', 'body_summary');
		$query->addField('ff', 'name', 'body_format');
		$query->addField('url_alias','dst');
		$query->addField('uc_products','model');
		$query->addField('commerce_product','product_id');
		$query->addField('files','	filename');
						
		$query->condition('n.type', $arguments['type'])
		->distinct();
	
		$this->source = new MigrateSourceSQL($query, array(), NULL, array('map_joinable' => FALSE));

		//////////////////////////////////////////////////////////////////////
   	// 3. Doel
   	//////////////////////////////////////////////////////////////////////
   	//
		$this->destination = new MigrateDestinationNode('product_display');


		//////////////////////////////////////////////////////////////////////
	 	// 4. Field Mapping
	  	//////////////////////////////////////////////////////////////////////
		
		// body, body:summary & body:format
		//////////////////////////////////////////
		//
		$this->addFieldMapping('field_productnode_body', 'body_value')
		      ->defaultValue('');
		 $this->addFieldMapping('field_productnode_body:summary','body_summary');
		 $this->addFieldMapping('field_productnode_body:format', 'body_format');
		
		 
	 	// body:language
		//////////////////////////////////////////
		//
		// Deze bestaat niet op de doellocatie
		//
	  	// $this->addFieldMapping('body:language')->defaultValue('nl'); // Aangepast


		// created & changed
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('created', 'created');
		$this->addFieldMapping('changed', 'changed');


		// comment
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('comment')->defaultValue(TRUE);


		// daycount
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('daycount')->issueGroup(t('DNM'));


		// Image
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('field_productnode_afbeelding','filename');
	 	$this->addFieldMapping('field_productnode_afbeelding:title','title');
	 	$this->addFieldMapping('field_productnode_afbeelding:alt','title');
	 	$this->addFieldMapping('field_productnode_afbeelding:file_class')->defaultValue('MigrateFileUri');
	 	$this->addFieldMapping('field_productnode_afbeelding:preserve_files')->defaultValue(TRUE);
	 	$this->addFieldMapping('field_productnode_afbeelding:destination_dir')->defaultValue('public://');
	 	$this->addFieldMapping('field_productnode_afbeelding:destination_file')->issueGroup(t('DNM'));	 	
	 	$this->addFieldMapping('field_productnode_afbeelding:source_dir')->defaultValue('/var/www/kbo.dvb/sites/default/files');
	 	
		// nid
		//////////////////////////////////////////
		//
		// Waar is dit voor? Producten worden toch allemaal van UID(1)
	 	// $this->addFieldMapping('field_product', 'nid')
		//      ->sourceMigration($arguments['products']);


		// is_new
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('is_new')->issueGroup(t('DNM'));


		// language
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('language',NULL)->defaultValue('nl');


		// log
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('log',NULL)->defaultValue(0);


		// metatag-stuff
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('metatag_abstract')->issueGroup(t('DNM'));
	 	$this->addFieldMapping('metatag_author')->issueGroup(t('DNM'));
	 	$this->addFieldMapping('metatag_cache-control')->issueGroup(t('DNM'));
	 	$this->addFieldMapping('metatag_canonical')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_content-language')->issueGroup(t('DNM')); 	
	 	$this->addFieldMapping('metatag_description')->issueGroup(t('DNM'));
	 	$this->addFieldMapping('metatag_expires')->issueGroup(t('DNM'));
	 	$this->addFieldMapping('metatag_generator')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_geo.position')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_geo.placename')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_geo.region')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_icbm')->issueGroup(t('DNM'));
	 	$this->addFieldMapping('metatag_image_src')->issueGroup(t('DNM'));
	 	$this->addFieldMapping('metatag_keywords')->issueGroup(t('DNM'));
	 	$this->addFieldMapping('metatag_news_keywords')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_next')->issueGroup(t('DNM')); 	
		$this->addFieldMapping('metatag_original-source')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_pragma')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_prev')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_publisher')->issueGroup(t('DNM'));
	 	$this->addFieldMapping('metatag_rating')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_referrer')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_refresh')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_revisit-after')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_rights')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_robots')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_shortlink')->issueGroup(t('DNM'));	
		$this->addFieldMapping('metatag_standout')->issueGroup(t('DNM'));
		$this->addFieldMapping('metatag_title')->issueGroup(t('DNM'));


		// path (2x!)
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('path', 'dst')->defaultValue(NULL);
	 	$this->addFieldMapping('field_productnode_pad', 'dst')->defaultValue(NULL);


		// product_referentie
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('field_product_referentie', 'product_id');


		// productnode_automaticstop
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('field_productnode_automaticstop')->issueGroup(t('DNM'));


		// productnode_dim1, productnode_dim2, 
		// productnode_dim3, productnode_dimnote
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('field_productnode_dim1')->issueGroup(t('DNM'));
	 	$this->addFieldMapping('field_productnode_dim2')->issueGroup(t('DNM'));
	 	$this->addFieldMapping('field_productnode_dim3')->issueGroup(t('DNM'));
	 	$this->addFieldMapping('field_productnode_dimnote')->issueGroup(t('DNM'));


		// productnode_productsoort
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('field_productnode_productsoort',NULL)->defaultValue('Koolborstels 1.x');


		// productnode_sku
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('field_productnode_sku','model');


		// promote
		//////////////////////////////////////////
		//
		// Promoted to front page?
		//
	 	$this->addFieldMapping('promote')->defaultValue(1);


		// revision
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('revision',NULL)->defaultValue(0);


		// revision_uid
		//////////////////////////////////////////
		//
		// Nodig?
	 	// $this->addFieldMapping('revision_uid')->issueGroup(t('DNM'));


		// status
		//////////////////////////////////////////
		//
		// Published or not?
		//
	 	$this->addFieldMapping('status', 'status')->defaultValue(1);


		// sticky
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('sticky');


		// timestamp
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('timestamp')->issueGroup(t('DNM'));


		// title
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('title', 'title');


		// tnid
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('tnid')->issueGroup(t('DNM'));


		// totalcount
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('totalcount')->issueGroup(t('DNM'));


		// translate
		//////////////////////////////////////////
		//
	 	$this->addFieldMapping('translate')->issueGroup(t('DNM'));


		// UID
		//////////////////////////////////////////
		//
	   $this->addFieldMapping('uid')->defaultValue(1);

  } // public function __construct(array $arguments) 

	//////////////////////////////////////////////////////////////////////
	// PrepareRow-procedure voor body_format & path
	//////////////////////////////////////////////////////////////////////
	//
	public function prepareRow($row) 
	{
		// Body-format
		///////////////////////////////////////////////
		//
		// * Transform body format from human-readable-name to machine-name
		// * There is a straightforward function for this
		//
    	if (!filter_format_exists($row->body_format)) 
    	{
      	$row->body_format = $this->transformFormatToMachineName($row->body_format);
    	}
    	
    	// Path
		///////////////////////////////////////////////
		//
		// * Er wordt een aparte query gebruikt om het pad uit url_alias te vissen
		// * Resultaat wordt opgeslagen in path
		//
		// → Werkt niet → M'n database-hack gebruiken
		//
		// $query_string = 'SELECT dst AS alias FROM {url_alias} WHERE src = :source';
    	// $connection = commerce_migrate_ubercart_get_source_connection();
    	// $url_alias = $connection->query($query_string, array(':source' => "node/{$row->nid}"))->fetchObject();
    	// if (!empty($url_alias->alias)) 
    	// {
      // 	$row->path = $url_alias->alias;
    	// }
    	
	} // Eind PrepareRow

	// Map friendly name of format (key) to machine name (format).
	//
   public $format_mapping = array();

	// Body-format: Moet nog iets gebeuren
	///////////////////////////////////////////////	
	//
   // Transform a D6-type format into a D7 format name
   //
   // @param $format
   // Friendly name of the format, like 'Filtered HTML'.
   //
  	public function transformFormatToMachineName($format) 
  	{
    	// If we haven't initialized the $format_mapping, do it.
    	if (empty($this->format_mapping)) 
    	{
      	$result = db_query("SELECT format, name FROM {filter_format}");
      	foreach ($result as $record) 
      	{
        		$this->format_mapping[$record->name] = $record->format;
      	}
    	}
    	if (!empty($this->format_mapping[$format])) 
    	{
      	return $this->format_mapping[$format];
    	}
    	return variable_get('commerce_migrate_ubercart_default_filter_format', 'plain_text');
  	}

}

CommerceMigrateUbercartCustomerBillingProfile

  • Customer billing profiles behelst migratie van adresgegevens vanuit Übercart-orders naar Drupal Commerce klant-profielen
  • Omdat in Drupal Commerce adresgegevens niet als onderdeel van orders worden opgeslagen, maar in aparte klantprofielen, moeten deze adresgegevens gemigreerd worden, voordat orders gemigreerd kunnen worden.

Bron

  • De bron is gemakkelijk: Alle velden tav. shipping address en billing address zitten in orderkopjes. Dat is tabel uc_orders
  • Deze gegevens worden voor elke order afzonderlijk bijgehouden

Doel - database-model

  • Adressen worden bijgehouden in field_data_commerce_customer_address. Elk record is een adres, met entity_id als primaire sleutel
  • Tabel field_data_commerce_customer_billing en field_data_commerce_customer_shipping zijn lookup-tabellen, met verwijzingen naar field_data_commerce_customer_address.

Voorbeeld:

  • Tabel users: UID=721: Ene meneer of mevrouw Chapman
  • Tabel commerce_order UID=721 heeft één order, met order_id=83 en order_number=2015-7. Het veld data bevat flink wat serialised info, en dat lijkt allemaal betrekking te hebben op de betaling
  • Tabel commerce_order_revision bevat maar liefst 13 records voor order_id=83
  • Tabel kolibrie.commerce_customer_profile bevat 2 records voor uid=721. Een shipping-profiel met profile_id/revision_id=59, en een billing-profiel met profile_id/revision_id=60
  • Tabel field_data_commerce_customer_address bevat voor entity_id=59 inderdaad het shipping-adres van dit persoon, en voor entity_id=60 het andere adres
  • Tabel field_data_commerce_customer_billing heeft een record met entity_id=59 met commerce_customer_billing_profile_id=52 (geen idee wat dat is)
  • Tabel field_data_commerce_customer_shipping heeft een record met entity_id=60 met commerce_customer_billing_profile_id=51 (geen idee wat dat is).

Doel - Migrate

  • Het argument voor MigrateDestinationEntityAPI::getKeySchema('commerce_customer_profile') was me wat onduidelijk, maar het maakt dus niet uit: Omdat er maar één tabel met adressen is, is het enige verschil tussen shipping en billing, het type. Dit genoemde schema is dus prima.

Migratie & redundantie

De module migreert alleen unieke records. Da's een mazzeltje, anders zou je een enorme hoeveelheid redundante adressen per klant kunnen krijgen!

Shipping vs. billing addresses?

Als de gebruiker aangeeft dat primair billing addresses worden bijgehouden, neemt de module alleen billing addresses over. Als de gebruiker aangeeft dat de de site primair shipping addresses gebruikt, worden deze overgenomen. Waarom niet allebei?

CMU-home page:

Customer billing profiles are created from the billing info of each Ubercart order. 
If you have a user migration (perhaps defined in the migrate_d2d wizard) you can 
maintain the user reference in each order.

Uitleg bij de dropdown-box met de keuze tussen shipping en billing:

It is possible to have more than one customer profile type in Drupal Commerce. 
Select the one you want to use for the incoming data.

Voor de hand liggende oplossing in dit geval: De taak klonen en aanpassen → Laat maar: Veel te ingewikkeld!

Niet zelf velden specificeren

Deze taak werk basically out of the box. Je hoeft in ieder geval niet zelf velden toe te voegen.

Gebruik patch of devel-versie

Pas deze patch toe, of gebruik wellicht de devel-versie van deze module.

CommerceMigrateUbercartOrder

  • Op 12 april 2016 (kbo208.dvb): Een flink aangepaste variant van de code gebruikt: Werkte totaal niet
  • Op 12 april 2016 (kbo209.dvb): Een niet-aangepaste versie van de code gebruikt: Werkte → Ik maakte het te moeilijk.

CommerceMigrateUbercartTransaction

Resultaat: Join commerce_order & commerce_payment_transaction
  • Taak CommerceMigrateUbercartTransaction migreert betalingstransacties
  • Ook als de betreffende betaalmethode niet is geconfigureerd op de doellocatie, worden de transacties evengoed gemigreerd
  • Transacties worden bijgehouden in tabellen commerce_payment_transaction en commerce_payment_transaction_revision:
select 

	commerce_order.order_id, 
	commerce_payment_transaction.payment_method,
	amount,
	message

from commerce_order
inner join commerce_payment_transaction 
        on commerce_order.order_id = commerce_payment_transaction.order_id

Taxes

De belangrijkste bron - Mogelijk de enige - is Deze issue-queue posting:

Taxes are implemented as price components in Commerce, not line items. So, during the product line 
item import, in the prepare() method we run a sub query that loads any Ubercart tax line items and 
we add them as price components. I also added a somewhat crude mechanism to map Ubercart tax names
to Commerce tax rate machine names (see the "Mappings" section below). I would strongly recommend 
using this. If you don't map old tax names to new rates, the tax price components show up with no 
name when viewing an order. I couldn't see a way around this as there is no generic tax rate that 
I can use as a place holder.

De bijbehorende patch is toegepast in bestand line_item.inc. Het werkt in mijn ervaring feilloos.

Bezorgkosten

Bezorgkosten worden verwerkt door ...

Mapping UID's & Machine name

Met machine name wordt de interne benaming van een object bedoeld, zoals in dit geval de benaming van velden
Een vermoedelijk voorbeeld van machine names: Ik heb een aantal taken verwijderd. De melding omtrent deregistering vermeldt mogelijk de machine names
Nog een voorbeeld: De migrate_d2d-taak om gebruikers te migreren, heet vermoedelijk b755a3db1User. Deze is afhankelijk van taak b755a3db1Role. Zo lang taken niet worden ge-dergistered, zijn ze te bereiken onder deze codes
En dit geval heeft de ontwikkelaar van de module voor een elegante machinenaam gezorgd: CommerceMigrateUbercartOrder. Ook handig voor gebruik via drush: dursh mi CommerceMigrateUbercartOrder

Orders worden gekoppeld aan gebruikers via UID's. Tijdens eerste tests met deze module, bleken orders niet met de correcte gebruikers te zijn geassociëerd. Ik vermoed dat deze module dat echter wel kan. Diverse mogelijkheden bij elkaar:

  • Ownership mapping: Zie verderop
  • UID's meemigreren - Meest voor de hand liggende oplossing, maar bewerkelijk: Je moet een database vrij veel geweld aandoen om de waardes van een auto-increment-veld aan te passen
  • Orders & gebruikers koppelen ahv. andere velden (bv. naam+emailadres)
  • Vantevoren UID's rechttrekken op bronlocatie
  • Order-UID's naderhand bijwerken.

Tijdens het configureren van de bron-database in CMU, kun je de optie User ownership should be mapped aanvinken. Dan verschijnt deze uitleg:

(required) When user ownership is being mapped it is necessary to create a user migration
(using something like migrate_d2d module) and put the machine name here. Incoming uids 
will then be mapped using that migration. You must previously have run this migration 
before importing.

De home page van de CMU-module bevat een cryptische referentie naar migrate_d2d. Ik vermoed dat dit over dezelfde oplossing gaat:

...It is probably a good idea to use migrate_d2d's "wizard" to create user and files migrations
that run first, and then can be referenced as a "source migration" for the fields you add to 
preserve mappings to the old content as often these get new uid/fid/nids in the new system.

There is an option in the commerce_migrate_ubercart page to put the machine name of your user 
migration if you want the previous purchases to link to the user's account.

Oftewel, wat-ie wil weten:

De machinenaam van de betreffende migrate_d2d "source migration" oftewel "user migration"

Mogelijk de beste bron die ik ken voor CMU [1]:

Once you have all of your classes visible to the module, you must go to the 
commerce_migrate_ubercart configure page and put in your settings. You will 
need a second database in your settings.php. If you want to map users, put 
the machine name of your user migration. Don't have one? Use migrate_d2d to 
create one.

Oftewel: De user migration machine name wordt automatisch gecreëerd als je met migrate_d2d aan de slag gaat.

Bestanden migreren

Ik vermoed dat er een kleine fout zit in de module voor het kopiëren van bestanden, gezien de foutmeldingen die ik zoal krijg:

The specified file /var/www/sites/default/files/files//13.jpg could not be copied to 
public://files//13.jpg: "copy(/var/www/sites/default/files/files//13.jpg): 
failed to open stream: No such file or directory File 
/var/www/example.com/sites/all/modules/migrate/plugins/destinations/file.inc, line 393"

Specificatie additionele database

De module vereist dat een tweede database wordt gespecificeerd in settings.php. Voorbeeld:

$databases = array (
  'default' => 
  array (
    'default' => 
    array (
      'database' => 'ubercart_to_commerce_d7',
      'username' => 'root',
      'password' => 'PASSWORD',
      'host' => 'localhost',
      'port' => '',
      'driver' => 'mysql',
      'prefix' => '',
    ),
  ),
  'ubercart' => 
  array (
    'default' => 
    array (
      'database' => 'ubercart_to_commerce_d6',
      'username' => 'root',
      'password' => 'PASSWORD',
      'host' => 'localhost',
      'port' => '',
      'driver' => 'mysql',
      'prefix' => '',
    ),
  ),
);

Nog een voorbeeld:

$databases = array 
(

	/********************/
	/* Default database */
	/********************/

	'default' => 
	array 
	(
		'default' => 
		array 
		(
			'database' => 'kbo2',
      	'username' => 'kbo2',
      	'password' => 'kbo2',
      	'host' => 'localhost',
      	'port' => '',
      	'driver' => 'mysql',
      	'prefix' => '',
    	),
 	), 
	/* End default database */

	/****************************/
	/* Übercart source database */
	/****************************/
	
	'ubercart' =>
	array 
	(
 		'default' => 
    	array 
		(
	      'database' => 'kbo',
   	   'username' => 'kbo',
   	   'password' => 'kbo',
	      'host' => 'localhost',
   	   'port' => '',
   	   'driver' => 'mysql',
   	   'prefix' => '',
    	),
	),
	/* End Übercart database */

);

Zie ook

Bronnen

Algemeen

GetMap 200-foutmelding

Machine name

Mapping order ownership

Billing profiles - Alle CMU-issues

Taxes - Alle CMU-issues