Problem/Motivation

It should be possible to import addresses for Ubercart Addresses via a CSV-file.

Proposed resolution

I plan to provide this functionality by writing an extension to Feeds. With that module you can import data not only from CSV, but also from other sources.

Remaining tasks

  • #1831424: Turn Ubercart Addresses address into an entity
    Addresses should first be implemented as entities, because the import functionality in Feeds (7.x-2.x) works with entities only.
  • Implement a Feeds processor for importing addresses.
  • Let the Ubercart Addresses field handlers specify how fields should be mapped.
  • Make sure that an user of an address can be identified by at least uid, guid (special field in Feeds), username or e-mail address.
  • Write a test that tests the address import functionality.

User interface changes

In the Feeds user interface an additional Feeds processor will become available that is called "Ubercart Addresses processor".

API changes

The field handlers of Ubercart Addresses will get an additional method to handle the field mapping. This method will probably be called getMappingTargets().

Current status

Original report by zorax

Hi,
I use you wonderful module, but my customer need to add is own user book with adresses.

With node import or user import I can create new user but only (name, mail..).
How can I import adresses from a csv file for example?
Regards,

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

MegaChriz’s picture

Assigned: Unassigned » MegaChriz
Status: Active » Postponed

I have made an experimental extension for User Import to import addresses upon user creation in the past. See my module here: #709726: Support for ubercart addresses (posted in #1). This module works only when importing users, it's not designed to import addresses after that. Also, the Extra Fields Pane integration code that is in there is outdated and will not work with the current version of Extra Fields Pane.

I have also been working on an extension module to import addresses with Feeds. This module will be compatible with Ubercart Addresses 6.x-2.x only. I have not published it yet, because I have not updated it for the latest revision of Ubercart Addresses 6.x-2.x yet. I'm too busy with getting Ubercart Addresses 6.x-2.x itself finished first.

However, if you have direct access to the database with phpMyAdmin, you could import addresses directly in the uc_addresses table. A few downsides:
- No addresses will be marked as default.
- For the country and zone fields you will first have to lookup their ID (check the uc_countries and the uc_zones tables).

I plan to finish the extension module to import addresses with Feeds in the future, but it will not be there before the first alpha release of Ubercart Addresses 6.x-2.x (which I hope to release before the end of this year).

zorax’s picture

thank you for this !
I will add Adresses directly with phpAdmin,
regards,

Michael-IDA’s picture

Sticking stub here for now:

Example, basic user migration into empty new system, keeping existing UID from old system.

- Pseudo code
- Most likely has syntax errors
- Most likely has logic errors
- One off
- Not efficient
- Throw away script
- Cut and paste what you need
- {how many more ways do I have to tell you...}

Usage: php -f user-import.php

File: user-import.php
<?php
chdir('/var/www/html/'); //set the working directory to your Drupal root
define('DRUPAL_ROOT', getcwd()); //set DRUPAL_ROOT for Drupal
require_once './includes/bootstrap.inc'; //require the bootstrap include
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); //Load Drupal (loads everything, but doesn't render anything)
require "mysql_connect.php"; //Need the Client database info

// Single user
$xuserid_begin = "33296";
$xuserid_end = "33296";
// All users
// $xuserid_begin = "1";
// $xuserid_end = "99999";

for ($i=$xuserid_begin; $i<=$xuserid_end; $i++) {
	user_delete($i);
}

// Log or other processing files if you want
$File01 = "somelogfilename".$xuserid_begin."-".$xuserid_end.".log";
$fh01 = fopen($File01, 'w');

$File03 = "bad-email-list".$xuserid_begin."-".$xuserid_end.".log";
$fh03 = fopen($File03, 'w');


// Zones and Countries can be pulled from their tables instead of hardcoded.

// Stripped from uc_zones "Stores state/province information within a country."
$zone_state_array = array(
	'1' => 'AL', '2' => 'AK', '3' => 'AS', '4' => 'AZ', '5' => 'AR', '6' => 'AF', '7' => 'AA', '8' => 'AC', '9' => 'AE', '10' => 'AM',
	'11' => 'AP', '12' => 'CA', '13' => 'CO', '14' => 'CT', '15' => 'DE', '16' => 'DC', '17' => 'FM', '18' => 'FL', '19' => 'GA', '20' => 'GU',
	'21' => 'HI', '22' => 'ID', '23' => 'IL', '24' => 'IN', '25' => 'IA', '26' => 'KS', '27' => 'KY', '28' => 'LA', '29' => 'ME', '30' => 'MH',
	'31' => 'MD', '32' => 'MA', '33' => 'MI', '34' => 'MN', '35' => 'MS', '36' => 'MO', '37' => 'MT', '38' => 'NE', '39' => 'NV', '40' => 'NH',
	'41' => 'NJ', '42' => 'NM', '43' => 'NY', '44' => 'NC', '45' => 'ND', '46' => 'MP', '47' => 'OH', '48' => 'OK', '49' => 'OR', '50' => 'PW',
	'51' => 'PA', '52' => 'PR', '53' => 'RI', '54' => 'SC', '55' => 'SD', '56' => 'TN', '57' => 'TX', '58' => 'UT', '59' => 'VT', '60' => 'VI',
	'61' => 'VA', '62' => 'WA', '63' => 'WV', '64' => 'WI', '65' => 'WY', '66' => 'AB', '67' => 'BC', '68' => 'MB', '69' => 'NL', '70' => 'NB',
	'71' => 'NS', '72' => 'NT', '73' => 'NU', '74' => 'ON', '75' => 'PE', '76' => 'QC', '77' => 'SK', '78' => 'YT',
);

// Stripped from uc_countries "Stores country information."
$country_id_array = array(
	'124' => 'CA',
	'840' => 'US',
);

mysql_select_db(DB_NAME_CDB, $dbcdb) or die ('Could not select DB_NAME_CDB: ' . mysql_error() );
$quer_outer=mysql_query("
SELECT DISTINCT a.id, a.*, b.drupal_tz
FROM Customers a, cdb_zip_timezone b
WHERE a.id >= '$xuserid_begin'
AND a.id <= '$xuserid_end'
AND ( ( a.Country = '' ) OR ( UCASE(a.Country) = 'US' ) )
AND EXISTS (SELECT cust_id FROM Orders WHERE a.id = Orders.cust_id)
AND (SELECT LEFT(Orders.date, 4) FROM Orders WHERE a.id = Orders.cust_id ORDER BY Orders.date DESC LIMIT 1) > '$year_cutoff'
AND a.BillZipCode = b.zip
ORDER BY a.id ASC
");
echo mysql_error();

while($row_outer=mysql_fetch_array($quer_outer)){

	$CurrUID=$row_outer['id'];

// Validate email address, skip user if bad
	if (valid_email_address($row_outer['EmailAddress'])) {
		$UserOrigEmail=$row_outer['EmailAddress'];
	} else {
		echo "Bad Email \n";
		$badmojo=$row_outer['id']."|".$row_outer['EmailAddress']."|EOL\n";
		fwrite($fh03, $badmojo);
		continue;
	}

// Create the base user account
	$NewUser = array(
		'uid' => $CurrUID,
		'name' => trim($row_outer['FirstName'])." ".trim($row_outer['LastName']),
		'pass' => user_password(6),
		'mail' => $UserOrigEmail,
		'signature_format' => 'filtered_html',
		'status' => 1,
		'timezone' => $row_outer['drupal_tz'],
		'language' => 'en',
		'init' => $UserOrigEmail,
		'roles' => array(2 => 'authenticated user', 10 => 'Customer'),
		'field_user_fname' => array('und' => array(array('value' => trim($row_outer['FirstName'])))),
		'field_user_lname' => array('und' => array(array('value' => trim($row_outer['LastName'])))),
	);

	user_save(NULL, $NewUser);


// Create the user's address book and save their addressess
// The address book API:  drupal.org/node/1340686
// Full Field list: aid, uid, first_name, last_name, phone, company, street1, street2, city, zone, postal_code, country, address_name, default_shipping, default_billing, created, modified

// Get the address book instance for user $uid or create if one doesn't exist
	$addressBook = UcAddressesAddressBook::get($CurrUID);

// Create the Shipping and Billing address vars.
	$addressShipping = UcAddressesAddressBook::get($CurrUID)->addAddress();
	$addressBilling = UcAddressesAddressBook::get($CurrUID)->addAddress();

// Do Shipping
	$addressShipping->setAsDefault('shipping');
	$addressShipping->setName('address_name');
	
	$addressShipping->setField('first_name', 'xx');
	$addressShipping->setField('last_name', 'xx');
	$addressShipping->setField('phone', 'xx');
	$addressShipping->setField('company', 'xx');
	$addressShipping->setField('street1', 'xx');
	$addressShipping->setField('street2', 'xx');
	$addressShipping->setField('city', 'xx');

	$zone_lookup = array_search(strtoupper('TX'), $zone_state_array);
	$addressShipping->setField('zone', $zone_lookup);
	$addressShipping->setField('postal_code', 'xx');
	$country_lookup = array_search(strtoupper('US'), $country_id_array);
	$addressShipping->setField('country', '$country_lookup');

// Do Billing
	$addressBilling->setAsDefault('billing');
	$addressBilling->setName('address_name');
	
	$addressBilling->setField('first_name', 'xx');
// ...

// Save all addresses in the address book
	$addressBook->save();

// Need to do more stuff for this user?
	$xcustkey = $row_outer['skey'];
	$quer_inner=mysql_query("SELECT distinct(skey), id, cust_key, added, origin, width, height, orig_ext, name FROM Images WHERE cust_key = '$xcustkey' AND in_library = 1 AND orig_ext='jpg' ORDER BY added DESC ");
	echo mysql_error();
	$str_user_pic="";
	while($row_inner=mysql_fetch_array($quer_inner)){
		// Process user's images...
		// Log the image info, add it to Drupal, etc.
		$str_user_pic=$CurrUID."|".$row_inner['skey'].".".$row_inner['orig_ext']."\n";
		fwrite($fh01, $str_user_pic);
	}
}

fclose($fh01);
// fclose($fh02);
fclose($fh03);
exit();
?>
File: mysql_connect.php
<?php
define ('DB_USER_CDB', 'root');
define ('DB_PASSWORD_CDB', 'rootpassword');
define ('DB_HOST_CDB', 'localhost');
define ('DB_NAME_CDB', 'clientDBname');

$dbcdb = @mysql_connect (DB_HOST_CDB, DB_USER_CDB, DB_PASSWORD_CDB) or die('Failure: ' . mysql_error() );

define ('DB_USER_DRU', 'root');
define ('DB_PASSWORD_DRU', 'rootpassword');
define ('DB_HOST_DRU', 'localhost');
define ('DB_NAME_DRU', 'clientDBname');

$dbdru = @mysql_connect (DB_HOST_DRU, DB_USER_DRU, DB_PASSWORD_DRU) or die('Failure: ' . mysql_error() );
?>
MegaChriz’s picture

Title: How to import Adresses by a file » How to import Addresses by a file
Version: 6.x-1.0 » 6.x-2.x-dev
Category: support » feature
FileSize
11.82 KB

OK, I had assumed that you was writing an extension to an existing module for importing data. Well, here is a sketchy version of the Feeds extension that I had made last year, when the Address book API was not yet finished. In the code some methods are called that were removed or renamed later, thus it isn't working code, but if someone wants to built it further before I will have the time do so, they will have some starting code here.

Michael-IDA’s picture

> I had assumed that you was writing an extension to an existing module for importing data.

No, I gave up trying to do that a long time ago, PHP is reasonably static and Drupal changes every major release...

MegaChriz’s picture

Title: How to import Addresses by a file » Address import / Feeds integration
Version: 6.x-2.x-dev » 7.x-1.x-dev

I have updated the issue summary. I'm moving to this to 7.x-1.x because this feature should first be fixed in that branch. After that it may be back ported to the 6.x-2.x branch if enough people like to have this feature in the 6.x-2.x version as well. Note that in #4 a "draft" of the 6.x-2.x implementation is there which may work with a few modifications.

MegaChriz’s picture

Status: Postponed » Needs review
FileSize
22.42 KB

First patch for Feeds integration. You will be needing the latest dev of Feeds or else you may get a PHP fatal error if at least one address fails to import (see also #1953008-2: PHP Fatal error: Nesting level too deep - recursive dependency? in FeedsProcessor.inc on line 199).

The patch includes:

  • A Feeds Processor for importing addresses.
  • A default importer (for a quick start).
  • User of an address can identified by ID, name and mail address.
  • Zones and countries can be identified by their ID, (English) name and code.
  • Options for marking addresses automatically as default shipping and/or default billing.
  • Changes to Ubercart Addresses field handlers: code is added for field mapping.
  • Small API additions in UcAddressesAddressBook and UcAddressesAddress.

To do:

  • Write automated tests (I already started with this, but it's not included with the patch).
  • Find a way to import countries by their native name, now you can only import them using the English name.

Let's see if the patch doesn't break any existing tests.

MegaChriz’s picture

I've the automated test for this issue as good as ready. In order to let the testbot execute the test, I first have added a dependency on the Feeds module for the test module. See commit.

New patch will follow.

MegaChriz’s picture

In addition of the patch in #7, this patch adds automated tests and support for using countries in their native name when importing addresses. Using native country names is not covered by the automated test as I was not sure how to write a test for that. With manual testing it looks like it's working.

MegaChriz’s picture

Some cleanup and text corrections.

If this passes tests, I think it's ready for commit.

MegaChriz’s picture

Version: 7.x-1.x-dev » 6.x-2.x-dev
Status: Needs review » Patch (to be ported)

Committed #10.

Additions for the address book API should be backported. The Feeds integration feature itself is not planned to be backported to the 6.x-2.x version.

MegaChriz’s picture

Status: Patch (to be ported) » Needs review
FileSize
1.72 KB

Address book API additions for the 6.x-2.x version. I don't expect it will fail tests, but you never know!

MegaChriz’s picture

Version: 6.x-2.x-dev » 7.x-1.x-dev
Status: Needs review » Fixed

Committed #12.

Setting version back to 7.x-1.x, cause that is the version the Feeds integration feature was added to.

Status: Fixed » Closed (fixed)

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

Anonymous’s picture

Issue summary: View changes

Updated issue summary to clarify the status of this issue: which tasks need to be done prior fixing this issue and what already is done at this point.

gurunathan’s picture

Hi All,

I have enabled Ubercart addresses module.
Now I want to import users via CSV file by using Feeds module.
"Ubercart addresses" module allows to import addresses via CSV file.
But I want to import both the user and user's address by using a single CSV file.
Is it possible?

MegaChriz’s picture

@gurunathan
You can use a single CSV file, but you have to you use two Feeds importers to import both users and addresses. First import all users (using the User processor) and then all addresses (using the Ubercart Addresses processor). For the address import, you can use the user's username or mail address to specify to which user the address belongs.

Your CSV file can look like this:

guid,user_name,user_mail,first_name,last_name,phone,company,street1,street2,city,zone,postal_code,country,address_name,default_shipping,default_billing

For the user import, you map at least "user_name" and "user_mail".
For the address import you map all the address fields you have "first_name", "postal_code", "country", etc plus either "user_name" or "user_mail".

Feeds will ignore columns from your CSV that you don't map, so you can have all the data in a single CSV file.