Storing Address information in User Profile

84 replies [Last post]
Joined: 09/07/2007
Juice: 75

I wanted to ask a few questions before I go too far down the wrong path. I have a few reasons that I need users address info outside of the online store. Specifically, we currently have a product registration system on our site that lets customers who previously purchased our products register them and get a free sheath to put the product in and protect it. We sell our products at several brick and mortar stores, so not everyone that registers a product will order it from us. We also use the contact information for people who register with us or people who purchase from our online store to send out promotional information if they desire it. Our current system stores all of the contact information in a user profile. Ubercart store this information for each order and not in a user profile.

I would like to have contact info stored in the users profile. This will allow someone to create a profile when they register their products, but be able to login later and purchase new products from our online store and have their contact info that they already entered be displayed. This would also work the other way, so when they buy a product, they can later register it by logging in with the same profile and their address info is already there because they have placed an order with us. It would also allow people to login and edit their contact info if it changes.

I understand the benefits for how Ubercart stores this information now. My question is, how hard would it be for me to change the checkout module to store the contact info in a different location in the database, but still work with Ubercart? I've done some research into how to store this info in user profiles, but what would be needed to get that to work with Ubercart?

I am currently looking into these modules for storing the profile info:
http://drupal.org/project/nf_registration_mod
http://drupal.org/project/nodeprofile
http://drupal.org/project/pageroute

1. Can this be done? (I guess anything CAN with enough work, but is it realistic)
2. What issues do you foresee with Ubercart?
3. Would other people find this useful?
4. If I can't figure out how to do it, could I pay someone to get it done?
5. Do you think a custom synchronization function could be used to populate one table if the other tables info already exists?

Thanks for your incite.

Joined: 08/07/2007
Juice: 15046

Don't have all the answers for you at this moment... I think I've been tossing ideas around in my head for what to do with addresses for a couple months now. Eye-wink One thing I would say is that it would be fairly trivial for a module to define custom checkout panes that load and store data for addresses in a different DB table and uses hook_order() to populate the order object with that data later on.

I guess ideally addresses would be stored in their own table and the individual order table, so you could alter an order's address however you need without affecting the other addresses but could also populate address fields from the stored address list. But like I said, haven't moved passed the brainstorming phase to make up a game plan for the changes.

Joined: 09/07/2007
Juice: 75

I did some digging into the Ubercart modules today and have a better understanding of how address data is stored and how it could work with user profiles.

My Observations (correct me if I'm wrong)
1. There is currently only one place that pulls old address data for use in the future. This is in the /cart/checkout form under the "Saved Addresses:" field. It does a database query on "uc_orders" for each unique address that a user has used in the past. That query is in the uc_get_addresses() function in uc_store.module.

2. Editing an order pulls the address info from the table "uc_orders" with the function uc_order_load() in uc_order.module.

My Proposal
1. I would like to create a module that adds a table "uc_user_addresses" to the database and contains a function uc_add_user_address() that will save an address to this table when an order is submitted.

2. The "uc_user_addresses" table would contain the same address info like "delivery_" and "billing_". It could also contain a "save_as" column that could be used to save multiple addressed like "Home", "Work", etc.

3. The module would only allow a single address per "save_as" type per user. Currently with the uc_get_addresses() function, you can have multiple addresses that could refer to the same location if they submit a second order and type in their address with a slight variation. For example, if it was "123 55th Ter." and they changed it to "123 55th Terr" when they submitted a second order.

4. In the check out form, add a "Save Address As:" drop down with a few "save_as" types.

5. Change the uc_get_addresses() function to use an if() statement to pull addressed from the new "uc_user_addresses" table if it exists, else, do the current query on the "uc_orders" table to provide backwards compatibility. This should allow the "Saved Addresses:" drop down on the /cart/checkout form to populate with either the new "uc_user_addresses" data or the "uc_orders" history data.

6. Add "Temporary Change" and "Permanent Change" radios to the /admin/store/orders/1/edit form. "Temporary Change" only updates the "uc_orders" table. "Permanent Change" updates both the "uc_orders" table and the "uc_user_addresses" table.

What do you think about this? It doesn't seem as hard as I thought it would be now that I've got it outlined. Although, I've never created a module before, but what better time to start then now?

Joined: 08/07/2007
Juice: 15046

Quick response as I head to bed on only two points (will have to think more about it all). Instead of user_addresses, I'd look for either customer_addresses or even just addresses. It may be best to just call it uc_addresses and don't force it to be connected to a user account so you can also store shipping and store addresses from other modules.

Also, I think I'd call the "save as" column "label" instead... that way you can ask them to specify a label which makes a little more logical sense.

Other than that, I think this is a good step in the right direction and am happy to have some other brain power helping me out. Smiling

Joined: 09/07/2007
Juice: 75

I've started to write this module and have run into a issue with the database table that I wanted to make sure was setup properly.

I've decided to create a table called uc_addresses like this:

<?php
function uc_addresses_install(){
 
drupal_set_message(t('Beginning installation of uc_addresses module.'));
  switch (
$GLOBALS['db_type']) {
    case
'mysql':
    case
'mysqli':
     
db_query("CREATE TABLE {uc_addresses} (
        uid mediumint(9) NOT NULL,
        location varchar(32) NOT NULL,
        first_name varchar(32) NOT NULL,
        last_name varchar(32) NOT NULL,
        phone varchar(32) NOT NULL,
        company varchar(64) NOT NULL,
        street1 varchar(64) NOT NULL,
        street2 varchar(64) NOT NULL,
        city varchar(32) NOT NULL,
        zone mediumint(9) NOT NULL,
        postal_code varchar(10) NOT NULL,
        country mediumint(9) NOT NULL,
        created int(11) NOT NULL,
        modified int(11) NOT NULL,
        PRIMARY KEY (uid)
        KEY location (location)
      ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "
);
     
$success = TRUE;
      break;
    case
'pgsql':
     
db_query("CREATE TABLE {uc_orders} (
        uid integer NOT NULL default 0,
        location varchar(32) NOT NULL default '',
        first_name varchar(32) NOT NULL default '',
        last_name varchar(32) NOT NULL default '',
        phone varchar(32) NOT NULL default '',
        company varchar(64) NOT NULL default '',
        street1 varchar(64) NOT NULL default '',
        street2 varchar(64) NOT NULL default '',
        city varchar(32) NOT NULL default '',
        zone integer NOT NULL defualt 0,
        postal_code varchar(10) NOT NULL default '',
        country integer NOT NULL default 0,
        created integer NOT NULL default 0,
        modified integer NOT NULL default 0,
        PRIMARY KEY (uid),
        KEY location (location)
      );"
);
     
$success = TRUE;
      break;
    default:
     
drupal_set_message(t('Unsupported database.'));
  }
  if(
$success){
   
drupal_set_message(t('The uc_addresses table was installed successfully.'));
  }
  else{
   
drupal_set_message(t('The installation of the uc_addresses module was unsuccessful.'),'error');
  }
}
?>

I think it is best to store each address in a separate row and not have two in a row like you have in the uc_orders table (delivery_field & billing_field). I have a column called "location" which will be used as the "label" or "save_as" mentioned above. I was looking at how Newegg.com has their user profile information setup and found out that you can have multiple addresses with the same label. You can create two addresses with the label "Home". The way I want to setup this module, the "location" will have to be unique. You cannot have more than one "Home", but you could have "Home2".

1. Do you think that it will be fine to force this field to be unique with form validation?

2. If you disagree, what would you use as a secondary KEY in the table so you will be able to select the proper row for a user which has multiple locations (Home, Home, Work, School, Billing, etc.)?

3. Newegg only allows one billing address per profile to be stored. I think this is fine. What do you think?

4. You mentioned above that this shouldn't be tied to a user so you can store address information from different modules. I'm having a hard time seeing any situations where there would not be a "uid" associated with an address. Can you elaborate on this?

Joined: 08/07/2007
Juice: 15046

1/2. I'm all for unique labels and forcing it in the form's validate handler. Make sure it's just checking against the other addresses for that user and not all the stored labels in general.

3. Regarding 1 billing address, I'm not sure. The thing is, someone may move or purchase using different credit cards that have different billing addresses. I guess I don't see a benefit in restricting it to just one.

4. What I'm referring to here is a fringe case. Normally, addresses will be entered by users for use on user accounts. But perhaps I want to save the store's mailing address to the table. I don't want that showing up in a user list, so I'd keep uid set to 0. For this reason, I still like the term "label" better than "location" which seems to narrow down the description of that column a little too much. In the end, it's just semantics, but you may need to store addresses for your store's mailing address, default pickup location for UPS, returned goods address, etc. and maybe these are all physically the same location but with different street2 lines for "Attn: Returns", "Attn: Billing", etc.

Joined: 09/07/2007
Juice: 75

3. I think the way Newegg handles it is to allow only one billing address to be stored, but you can always enter a different billing address when you check out, you just can't save it as a different one. As far as moving, you can edit the billing address, just not have more than one. I guess it doesn't matter either way since the code to have multiples will be there. I'm just assuming that there is a good reason that Newegg only allows one billing address.

4. If you wanted to store the store's addresses (ok that's confusing, let's call it store the business's address) you could use the administrator's account. Since you will be able to store multiple addresses and the administer will always be user 1 (I think it works that way), this shouldn't be a problem. Do you think that will work?

Joined: 09/07/2007
Juice: 75

I've finished creating this module. See the readme.txt for details. It will allow you to add addresses to a table from /user/'uid'. I would love to make a few minor changes to core to allow this module to be used during the order submit process.

Let me know what you think. There are a few minor //TODO's that could be completed. Most of which I don't know how to do yet. I based most of this off of uc_order and uc_cart. I don't know how to work with $_SESSION, so some of that might need to be added or removed.

AttachmentSize
uc_addresses.zip 10.78 KB
Joined: 11/30/2007
Juice: 25

This is the same issue I was just looking into writing my own module to handle. I've tested uc_addresses and it seems to work very well. I hope it will be added into core because this is exactly what I need! Be nice to see it working with the orders page and actually using this during the checkout process.

Joined: 09/07/2007
Juice: 75

I'll be doing some more development on this module soon. If it isn't added to core, I'll need to use hooks to override the checkout address entry forms.

I'm busy at the moment working on deploying an ERP solution. Currently looking at www.postbooks.org. If it works out, I should be able to finish this module sometime in January 08.

Joined: 12/13/2007
Juice: 21

Hi

There's a typo in the _install function; the database name is different for mysql and pgsql

Cheers

Joined: 01/22/2008
Juice: 9

Why are we using "mediumint(9)" for the uid when in Drupal's user table it's defined as "int(10) unsigned"? Does the fact that we're not doing any actual math on these values mean that they're functionally equivalent? Which should I use in these situations?

Joined: 09/19/2007
Juice: 89

I have modified my uc_store.module so that I can use the uc_addresses module during checkout:
Find the function in uc_store called uc_get_address and add these lines below after the line that looks like this:

Look for this line

<?php
$addresses
= array();
?>

Lines to add between the line above and the 'while' line

<?php
if($profile_addresses = uc_addresses_get_address($uid, NULL, 0)){
  foreach(
$profile_addresses AS $index => $profile_address){
   
$addresses[] = (array) $profile_address;
  }
  return
$addresses;
}
?>

This way, the addresses from the users profile that they have created will show in the checkout form. If there are none, the addresses from previous orders will show instead.

Cheers,
Gord.

Joined: 09/07/2007
Juice: 75

Thanks!

I hope you can get some use out of this. I've had to push back my time frame on this, but I will start work on finishing this module soon. I'll roll the above changes in and get the rest of the hooks setup.

Joined: 08/28/2007
Juice: 693

What kind of progress has been made on this? What are the outstanding tasks to complete the integration?

Joined: 09/07/2007
Juice: 75

I need to write the hooks that will override the address forms for the shopping cart check out screens. Their needs to be an option to save the address at check out if it is new and also select past used addresses from a drop down. I have the forms setup and the database connections made, so the only major thing to do is the hooks. There has been some talk about normalizing the addresses so the name is stored in a different table. I think that would be a good idea, but it would take a little bit more work.

Joined: 08/28/2007
Juice: 693

Sounds like you're almost there. One of our client is looking for this functionality and is shocked it's not part of Ubercart already. I tried to explain that it might be better to just leave things as they are but he's scared of being ripped off. Also, he needs to be able to only allow UK Mainland customers to checkout and all others to complete the order process without making payment. I guess this is an important step towards that otherwise the system would not know where the customer is based before checkout.

Do you have an idea when you'll get chance to look into the hooks? Do you need any help from me?

Joined: 09/07/2007
Juice: 75

If you read through this post, you'll see that I keep pushing this back. I would say end of March at the moment. I WILL finish this because we NEED the functionality on our site. So if you can wait, I'll make sure and let you know when it's done or when I need some beta testing. In the meantime, if you want to do some work on it, feel free to contribute what you can.

Thanks!

Joined: 08/28/2007
Juice: 693

Hi bendiy, Can you please provide what you have so I can have a look and see if I can integrate it? That is assuming you changed anything other than that mentioned above.

Joined: 09/07/2007
Juice: 75

It's all here in this post. I haven't done any code editing since the post. Some people above have pointed out above a few typo errors that still need to be fixed.

You might want to take a look at my post here if you haven't already:
http://www.ubercart.org/forum/development/3512/integration_postbooks_erp...

I want to "Normalize" the tables better than they are now. However, you might be able to get by with the functionality that is setup now and just adding the hook in to override the checkout forms.

Joined: 08/28/2007
Juice: 693

Hey man, just had a look into this and it's setup a little differently to what I was hoping. Let me just clarify this, the user has to register first and then manually manage their addresses.. they are not forced to enter an address when registering?

I'm going to have a look into how to intercept the user register process now.

Joined: 09/07/2007
Juice: 75

You are correct. That's another thing that needs to be done. There are a few modules that I was looking at that could automate the flow that a user would have to follow when registering. This module (uc_addresses) has the Nodes that the flow would force them to go through at registration.

Pageroute:
http://drupal.org/project/pageroute

Workflow: (i'm not sure if it will do this or not, but Ubercart already uses it for somethings)
http://drupal.org/project/workflow

It depends on how your site will work, but I would like to see uc_addresses be used to store and retrieve addresses at checkout. If a user is already registered, they can add an address when they check out, or use an address they have previously used. Just because a user registers on a site doesn't mean that they are going to use the store. Again, this depends on the type of site. If you have a forum, reviews or a wiki, they might just register for that. Therefore the user would not need to enter address info (Unless you want to force that for CRM use). I think it would be best to only capture address info at user registration if they are actually checking out and you are forcing them to login/create account.

I think you can achieve what it sounds like you want to do with the Pageroute module. I hope this helps.

Joined: 08/28/2007
Juice: 693

Thanks for that, bendiy.

The site I'm working on will need to use zone information to show different payment modules.

I've hacked the uc_addresses.module a bit and changed this function:

<?php
/**
* Implementation of hook_user().
*/
function uc_addresses_user($op, &$edit, &$account, $category = null){
  global
$user;
  switch (
$op){
    case
'view':
      if (
user_access('edit and view addresses') || $user->uid == $account->uid) {
       
$link = l('here', 'user/'. $account->uid .'/addresses');
       
$items = array();
       
$items['addresses'] = array('title' => t('Manage Addresses'),
         
'value' => 'Click ' . $link . ' to manage your addresses.',
         
'class' => 'member',
        );
        return array(
t('Addresses') => $items);
      }
      else {
        return
NULL;
      }
    case
'register':
     
$form['address'] = array(
       
'#type' => 'fieldset',
       
'#title' => t("Address"),
       
'#collapsible' => TRUE,
       
'#collapsed' => FALSE,
      );
     
$form['address']['label'] = uc_textfield('Save Address As', $arg1->label, TRUE);
      if (
uc_address_field_enabled('first_name')) {
       
$form['address']['first_name'] = uc_textfield(uc_get_field_name('first_name'), $arg1->first_name, uc_address_field_required('first_name'));
      }
      if (
uc_address_field_enabled('last_name')) {
       
$form['address']['last_name'] = uc_textfield(uc_get_field_name('last_name'), $arg1->last_name, uc_address_field_required('last_name'));
      }
      if (
uc_address_field_enabled('phone')) {
       
$form['address']['phone'] = uc_textfield(uc_get_field_name('phone'), $arg1->phone, uc_address_field_required('phone'), NULL, 32, 16);
      }
      if (
uc_address_field_enabled('company')) {
       
$form['address']['company'] = uc_textfield(uc_get_field_name('company'), $arg1->company, uc_address_field_required('company'), NULL, 64);
      }
      if (
uc_address_field_enabled('street1')) {
       
$form['address']['street1'] = uc_textfield(uc_get_field_name('street1'), $arg1->street1, uc_address_field_required('street1'), NULL, 64);
      }
      if (
uc_address_field_enabled('street2')) {
       
$form['address']['street2'] = uc_textfield(uc_get_field_name('street2'), $arg1->street2, uc_address_field_required('street2'), NULL, 64);
      }
      if (
uc_address_field_enabled('city')) {
       
$form['address']['city'] = uc_textfield(uc_get_field_name('city'), $arg1->city, uc_address_field_required('city'));
      }
      if (
uc_address_field_enabled('country')) {
       
$form['address']['country'] = uc_country_select(uc_get_field_name('country'), $arg1->country, NULL, 'name', uc_address_field_required('country'));
      }
      if (
uc_address_field_enabled('zone')) {
        if (isset(
$_POST['country'])) {
         
$country_id = intval($_POST['country']);
        }
        else {
         
$country_id = $arg1->country;
        }
       
$form['address']['zone'] = uc_zone_select(uc_get_field_name('zone'), $arg1->zone, NULL, $country_id, 'name', uc_address_field_required('zone'));
      }
      if (
uc_address_field_enabled('postal_code')) {
       
$form['address']['postal_code'] = uc_textfield(uc_get_field_name('postal_code'), $arg1->postal_code, uc_address_field_required('postal_code'), NULL, 10, 10);
      }
      return
$form; // address form isn't themed
   
case 'insert':
     
// insert address to database
     
return;
  }
}
?>

This makes the address form show up on user/register and the fields are validated as usual. How would be the correct way to insert the address into the uc_addresses table in the database?

Joined: 08/28/2007
Juice: 693

Okay, now I have the address saved to the uc_addresses table in the database. Also, I have added the delete query to delete the addresses associated with a uid. Posting here to get feedback. Gong to see if I can implement the saved addresses in checkout form bit posted above next.

<?php
/**
* Implementation of hook_user().
*/
function uc_addresses_user($op, &$edit, &$account, $category = null){
  global
$user;
  switch (
$op){
    case
'view':
      if (
user_access('edit and view addresses') || $user->uid == $account->uid) {
       
$link = l('here', 'user/'. $account->uid .'/addresses');
       
$items = array();
       
$items['addresses'] = array('title' => t('Manage Addresses'),
         
'value' => 'Click ' . $link . ' to manage your addresses.',
         
'class' => 'member',
        );
        return array(
t('Addresses') => $items);
      }
      else {
        return
NULL;
      }
    case
'register':
     
$form['address'] = array(
       
'#type' => 'fieldset',
       
'#title' => t("Address"),
       
'#collapsible' => TRUE,
       
'#collapsed' => FALSE,
      );
     
$form['address']['label'] = uc_textfield('Save Address As', $arg1->label, TRUE);
      if (
uc_address_field_enabled('first_name')) {
       
$form['address']['first_name'] = uc_textfield(uc_get_field_name('first_name'), $arg1->first_name, uc_address_field_required('first_name'));
      }
      if (
uc_address_field_enabled('last_name')) {
       
$form['address']['last_name'] = uc_textfield(uc_get_field_name('last_name'), $arg1->last_name, uc_address_field_required('last_name'));
      }
      if (
uc_address_field_enabled('phone')) {
       
$form['address']['phone'] = uc_textfield(uc_get_field_name('phone'), $arg1->phone, uc_address_field_required('phone'), NULL, 32, 16);
      }
      if (
uc_address_field_enabled('company')) {
       
$form['address']['company'] = uc_textfield(uc_get_field_name('company'), $arg1->company, uc_address_field_required('company'), NULL, 64);
      }
      if (
uc_address_field_enabled('street1')) {
       
$form['address']['street1'] = uc_textfield(uc_get_field_name('street1'), $arg1->street1, uc_address_field_required('street1'), NULL, 64);
      }
      if (
uc_address_field_enabled('street2')) {
       
$form['address']['street2'] = uc_textfield(uc_get_field_name('street2'), $arg1->street2, uc_address_field_required('street2'), NULL, 64);
      }
      if (
uc_address_field_enabled('city')) {
       
$form['address']['city'] = uc_textfield(uc_get_field_name('city'), $arg1->city, uc_address_field_required('city'));
      }
      if (
uc_address_field_enabled('country')) {
       
$form['address']['country'] = uc_country_select(uc_get_field_name('country'), $arg1->country, NULL, 'name', uc_address_field_required('country'));
      }
      if (
uc_address_field_enabled('zone')) {
        if (isset(
$_POST['country'])) {
         
$country_id = intval($_POST['country']);
        }
        else {
         
$country_id = $arg1->country;
        }
       
$form['address']['zone'] = uc_zone_select(uc_get_field_name('zone'), $arg1->zone, NULL, $country_id, 'name', uc_address_field_required('zone'));
      }
      if (
uc_address_field_enabled('postal_code')) {
       
$form['address']['postal_code'] = uc_textfield(uc_get_field_name('postal_code'), $arg1->postal_code, uc_address_field_required('postal_code'), NULL, 10, 10);
      }
      return
$form; // address form isn't themed
   
case 'insert':
     
db_query("INSERT INTO {uc_addresses} (uid, label, first_name, last_name, "
           
."phone, company, street1, street2, city, zone, postal_code, country, "
           
."created, modified) VALUES (%d, '%s', "
             
."'%s', '%s', '%s', "
             
."'%s', '%s', '%s', "
             
."'%s', %d, '%s', %d, "
             
."%d, %d)", $edit['uid'], $edit['label'],
              
$edit['first_name'], $edit['last_name'], $edit['phone'],
              
$edit['company'], $edit['street1'], $edit['street2'],
              
$edit['city'], $edit['zone'], $edit['postal_code'],
               ((
is_null($edit['country']) || $edit['country'] == 0) ? variable_get('uc_store_country', 840) : $edit['country']),
              
time(), time());
      return;
    case
'delete':
     
db_query("DELETE FROM {uc_addresses} where uid = %d", $account->uid);
      return;
  }
}
?>
Joined: 08/28/2007
Juice: 693

The temporary solution posted by Gord above works great. At first I thought it would be better to return the label for the address as the option in the list, but I feel it's more valuable to a user to see the first line of the saved address.

There of course is a much more important negative to this and it's that I now have a hacked Ubercart. Would I need to use hook_form_alter to inject the option to avoid hacking Ubercart or is this something that could be changed in uc_store.module? I think this saved addresses functionality is awesome and since a lot of clients are used to the way osCommerce works they expect this kind of behaviour (even though I do try to push anonymous checkout to them).

Someone please chime in and stop me talking to myself!

Joined: 12/28/2007
Juice: 677

Sorry I'm not a programmer to be able to help in coding, but just wanted to let you know I'm also looking forward to see the address-book functionality live in Ubercart (I'm targeting a Christmas gift shop)

Keep up the great work!

Joined: 08/28/2007
Juice: 693

HI Abilnet, thanks for at least keeping me company!

Joined: 09/07/2007
Juice: 75

My only critique is that you have added code from other parts of the module to the uc_addresses_user function. I'm not a programmer by trade, but I think the concept of Object Oriented Programing is to not reuse code, but to reference/use one function over and over to do what you need.

I think you can get your form for registration by using the uc_addresses_pane_address() function in the uc_addresses_address_pane.inc file. If you added another "case" to that function for 'register' (or just use the 'new' case) and added a hook_menu() to the uc_addresses_menu() function for the path that a user registers at, I think it would work.

You can also perform the insert into the database with the uc_addresses_add_address function near the bottom of uc_addresses.module.

It just makes it easier to maintain in the future.

I'm not sure how to override the forms at check out without editing the Core Ubercart code. That was my next step for development on this module. I know it can be done without making changes. There is a hook that should work, I just haven't had time yet to figure it out. I believe it is the hook_form_alter() function. You can probably use that to insert your address form at registration also. Just use hook_form_alter() to override the default registration form. You will have to make sure a recreate the existing form though (username, email, password, etc.), but you can copy from the default and just add to it.

I hope this helps. They just released the new version of PostBooks ERP system yesterday, so now I can finish the implementation I've been working on. Hopefully it will go smooth so I can finally finish this module soon.

Cheers!

Joined: 08/28/2007
Juice: 693
Quote:

My only critique is that you have added code from other parts of the module to the uc_addresses_user function. I'm not a programmer by trade, but I think the concept of Object Oriented Programming is to not reuse code, but to reference/use one function over and over to do what you need.

I understand this and I am striving for it but I'm not a native programmer either, just learning this stuff as I go along. At the moment, with my very basic skills I'm finding that I need different things returned, this could be due to the functions not being reusable enough or my lack of knowledge of how to manipulate things before they go into or after they come out of functions.

Quote:

I think you can get your form for registration by using the uc_addresses_pane_address() function in the uc_addresses_address_pane.inc file. If you added another "case" to that function for 'register' (or just use the 'new' case)...

Yes, this would seem like the best idea. I can add a new case to the switch in uc_addresses_pane_address() but using the 'new' case would be better. My only problem with using the 'new' case was that it returns some weird array which I'm not sure how to feed that into the $form. Should I be manipulating the returned value after it comes out of the function? As you can see I'm still stuck on how to theme the form properly. Insight on this would be appreciated.

Quote:

...and added a hook_menu() to the uc_addresses_menu() function for the path that a user registers at, I think it would work.

I guess this would be to fire off callback when a path is visited. Not quite sure what function but what I have done actually works pretty well, albeit with some pretty major bumps. I'm open to ideas so if you'd like to elaborate on this I would be more than happy to refactor things.

Quote:

You can also perform the insert into the database with the uc_addresses_add_address function near the bottom of uc_addresses.module.

It just makes it easier to maintain in the future.

The uc_addresses_add_address() function seems to take an object and not an array like $edit. If I knew how to convert an array into an object (which seems to be what uc_addresses_add_address() needs) then I would have used that. I just wanted to get this working so I could fine tune it later. Is there a standard way in PHP to convert an array into an object? Would it be better for the uc_addresses_add_address() function to accept both arrays and objects, or just arrays? Your input here would be good.

Quote:

I'm not sure how to override the forms at check out without editing the Core Ubercart code. That was my next step for development on this module. I know it can be done without making changes. There is a hook that should work, I just haven't had time yet to figure it out. I believe it is the hook_form_alter() function..

You are right, it is hook_form_alter(). I have been working on this and have the following:

<?php
function uc_addresses_form_alter($form_id, &$form) {
  global
$user;
  if (
$form_id == 'uc_cart_checkout_form') {
   
$addresses[] = array('Select one...');
   
$addresses[] = uc_get_addresses($user->uid, $type);
    if (
$profile_addresses = uc_addresses_get_address($user->uid, NULL, 0)) {
      foreach (
$profile_addresses as $index => $profile_address) {
       
$addresses[] = $profile_address;
      }
    }
   
dprint_r($addresses);
   
//dprint_r($form['panes']['delivery']['delivery_address_select']['#options']);
    //$form['panes']['delivery']['delivery_address_select']['#options'] = $addresses;
 
}
}
?>

The function uc_get_addresses() returns an array which is exactly what is needed but uc_addresses_get_address() returns an object. This is the same problem as above, I guess I need to loop over the elements in the object and output an array?

Sorry for my newbie questions, I'm working on it though!

Joined: 09/07/2007
Juice: 75

I'm in the same boat as you. This module is my first attempt at any PHP programming. Most of this code is pulled from the other Ubercart modules and it took me quite a while to trace it and figure out how to put it all together. What text editor are you using? I've found Eclipse to be really nice. It allows you to see where a function is used and make tracing code much easier.

Anyways... I'll try and give you an idea of how to do this for a new addrress.

rich wrote:

Yes, this would seem like the best idea. I can add a new case to the switch in uc_addresses_pane_address() but using the 'new' case would be better. My only problem with using the 'new' case was that it returns some weird array which I'm not sure how to feed that into the $form. Should I be manipulating the returned value after it comes out of the function? As you can see I'm still stuck on how to theme the form properly. Insight on this would be appreciated.

To use a form, there are 4 functions that it goes through.

  1. uc_addresses_menu() - Each of the elements (if, else) of this function refer to a path that a node is located at. The arg(1) are the parts of that path that can be passed into the function. You'll notice that there are some arg() that corrispond to 'new', 'view', 'edit'. These affect which functions is called next. You will need to add an element here for your path at registation that will call the next funciton.
  2. uc_addresses_new_address() - Is called by the agr() 'new' being part of the path however, you should use whatever the path is for registration and then send the next function 'new'. You will probably have to change the permissions check so the user doesn't have to be logged in if they are registering. This function outputs the form, but not before calling the next funcion
  3. uc_addresses_new_address_form() - This function returns the form as an array. If you send it $view as 'new' it will get the 'new' form from the function below.
  4. uc_addresses_pane_address() - This function builds the array for the form and also takes into account your switch statement for which case it is in ('new').

Two other functions that you might need:
theme_uc_address_new_address_form() - just wraps the form in some HTML
uc_addresses_new_address_form_submit() - is called above when a form is submitted. It calls the correct database function based on what type of action is taking place above (update, insert, delete). It will be sent $view = 'new', so should do a INSERT to the database.

Hopefully this doesn't confuse you more.

Joined: 08/28/2007
Juice: 693

Before I try to figure out what you're suggesting I'd like to post my updated hook_form_alter from above:

<?php
function uc_addresses_form_alter($form_id, &$form) {
  global
$user;
  if (
$form_id == 'uc_cart_checkout_form') {
   
$addresses = array('Select one...');
   
$addresses = array_merge($addresses, uc_get_addresses($user->uid, $type));
    if (
$profile_addresses = uc_addresses_get_address($user->uid, NULL, 0)) {
      foreach (
$profile_addresses as $index => $profile_address) {
       
$addresses[] = $profile_address;
      }
    }
   
dprint_r($addresses);
   
//$form['panes']['delivery']['delivery_address_select']['#options'] = $addresses;
 
}
}
?>
Joined: 08/28/2007
Juice: 693

Yes, this is pretty confusing for me.

Should I not be using hook_user() to inject the form elements? If I use the above method I would have to use hook_form_alter() to remove the submit buttons supplied by uc_addresses_new_address()?

http://api.drupal.org/api/function/hook_user/5

Quote:

What text editor are you using? I've found Eclipse to be really nice. It allows you to see where a function is used and make tracing code much easier.

I'm just using TextMate which I love, but it's not a proper debugger which I expect that is what you're talking about. I tried Eclipse but it was hard to learn compared to a text editor like TextMate.

Joined: 08/28/2007
Juice: 693

Does anyone else know about this stuff and could chime in with a better plan? Should I be using hook_user or assigning callbacks using hook_menu?

If I'm all good using hook_user then should I be converting an object to an array inline? Ryan, Lyle, anyone!

Joined: 08/07/2007
Juice: 6674

I haven't really gotten my head around what's going on here, but I can tell you the easy way to turn an array into an object:

<?php
  $array
= array('one' => 1, 'two' => 2, 'three' => 3);
 
$thingy = (object)$array;
 
print_r($thingy);
/*
Output: something like
   StdClass object {
      'one' => 1,
      'two' => 2,
      'three' => 3
   }
*/
?>

I don't know what happens when the array has numeric keys though. I'm not sure you can do $obj->2.

Give that a try and see how it works. I don't know if hook_user is the best way to do this or not, and i don't see where you've hacked the Übercart code, either. I figure if you can get it to work, we can see about making it better then.

Joined: 08/28/2007
Juice: 693

Ah-hah! Thanks for the array to object code, Lyle. This will make the job easier. Great to know it's so simple. I'll have to test it out to see if it will works with what I have.

Quote:

I don't know if hook_user is the best way to do this or not, and i don't see where you've hacked the Übercart code, either. I figure if you can get it to work, we can see about making it better then.

I am using:

  • hook_user()'s 'register' $op to inject the form elements into user/register
  • hook_user()'s 'insert' $op to add the address to the uc_addresses table
  • hook_user()'s 'delete' $op to remove all addresses associated with the $account->uid

It's working perfectly. But I don't know if I'm doing the right thing. I've used the hack presented by Gord above in uc_store.module:

<?php
function uc_get_addresses($uid, $type = 'billing') {
    ...

 

$addresses = array();
  if (
$profile_addresses = uc_addresses_get_address($uid, NULL, 0)) {
    foreach (
$profile_addresses as $index => $profile_address) {
     
$addresses[] = (array) $profile_address;
    }
  }
  while (
$address = db_fetch_array($result)) {
    if (!empty(
$address['postal_code'])) {
     
$addresses[] = $address;
    }
  }

  return

$addresses;
}
?>

I'll post back when I have more. Going to try your suggestion to see if I can remove that hack.

Joined: 08/28/2007
Juice: 693

That worked perfectly Lyle, thank you! I have another problem now, I'm trying to get the addresses without hacking uc_store.module. It's just I don't understand how to generate the array required.

What I have:

Array
(
    [0] => Array
        (
            [first_name] => Rupert
            [last_name] => Bear
            [phone] => 
            [company] => 
            [street1] => 1 East Road
            [street2] => 
            [city] => Middlestown
            [zone] => 3843
            [postal_code] => AA1 1AA
            [country] => 222
        )
)

What I need:

Array
(
    [0] => Select one...
    [{ "first_name": "Rupert", "last_name": "Bear", "phone": "", "company": "", "street1": "40 Eastbourne Road", "street2": "", "city": "Middlestown", "zone": "3843", "postal_code": "AA1 1AA", "country": "222" }] => 1 East Road
)

What kind of array is that and is there a built-in function I can use to generate them? Thanks again for all your help.

Joined: 11/06/2007
Juice: 213

That would be a javascript array/object if I'm not mistaken. Try the following:

<?php
// $array would contain all the address keys and their values
$jsarray = drupal_to_js($array);

// Then put it into the new array
$new_array = array(t('Select one...'), $jsarray => t('Address Name'));
?>
Joined: 08/28/2007
Juice: 693

Thanks for the example.

I asked in #php on freenode last night and Wolfpaws said he thought it was probably a JSON encoded array. So I was using json_encode() but thanks for drupal_to_js() which, in this case, does the same thing.

I've got a little bit more to do but almost there, so close! Let me finish what I can myself and I'll put it here for you all to test/improve.

Joined: 09/07/2007
Juice: 75

I'm not sure where you are using the form. If it is at registration, I think you could use the Pageroute module to force users through a series of nodes that call uc_addresses_new_address() with the hook_menu function uc_addresses_menu(). This would mean that you do not need to override the submit button. I'm not sure how your Drupal install is setup, but on mine, you have to enter a username and email, then click submit. When you get your password from your email, you could login and then be forced to enter your address info with the Pageroute module. It sounds like you might be trying to alter the registration form by adding the address form there.
http://localhost/user/register

With pageroute, you should be able to turn registration into a 2 node process. One for the username/email and one for the address form.

Joined: 08/28/2007
Juice: 693

Hi bendiy, I'd rather avoid using the pageroute module. We really need the address form on the user/register page. I've got it pretty much working so will just need to clean it up a bit (reuse the functions you have provided) and try to remove the hack. Will let you know how it goes, although might be on and off over the weekend.

Joined: 02/06/2008
Juice: 200

following this thread with great interest

Joined: 11/06/2007
Juice: 213

I'm on board to help out with this module in any way that I can.

Joined: 03/06/2008
Juice: 27

Hey folks.

There seems to be some good information and work being done on this code. Can either bendiy or rich create a rollup patch with all the work to date, i think it might help others figure out where you're at and help you if need be.

Thanks.

...alex...

Joined: 08/28/2007
Juice: 693

__Tango, I'll try to get what I have uploaded today.

Joined: 08/28/2007
Juice: 693

Okay, I've managed to finish it. Good timing because my girl wants me to help in the garden. This is basically what I changed:

<?php
/**
* Implementation of hook_user().
*/
function uc_addresses_user($op, &$edit, &$account, $category = null){
  global
$user;
  switch (
$op){
    case
'view':
      if (
user_access('edit and view addresses') || $user->uid == $account->uid) {
       
$link = l('here', 'user/'. $account->uid .'/addresses');
       
$items = array();
       
$items['addresses'] = array('title' => t('Manage Addresses'),
         
'value' => 'Click ' . $link . ' to manage your addresses.',
         
'class' => 'member',
        );
        return array(
t('Addresses') => $items);
      }
      else {
        return
NULL;
      }
    case
'register':
     
// get the address form
     
$form = uc_addresses_pane_address('new', $arg1, $arg2);
     
$form = array($form['contents']); // modify to what we need
     
$form[0]['#title'] = 'Address'; // rename the fieldset
     
return $form;
    case
'insert':
     
$address = (object)$edit;
     
uc_addresses_add_address($address);
      return;
    case
'delete':
     
db_query("DELETE FROM {uc_addresses} WHERE uid = %d", $account->uid);
      return;
  }
}

/**
* Implementation of hook_form_alter().
*
* Here we're going to override the saved address options on the checkout form
*/
function uc_addresses_form_alter($form_id, &$form) {
  global
$user;
  if (
$form_id == 'uc_cart_checkout_form') {
   
$options = array('0' => t('Select one...'));
   
// grab the addresses saved from previous checkouts
   
if ($addresses = uc_get_addresses($user->uid, $type)) {
      foreach (
$addresses as $address) {
       
$options[drupal_to_js($address)] = $address['street1'];
      }
    }
   
// grab the addresses saved at registration or added in the user profile
   
if ($addresses = uc_addresses_get_address($user->uid, NULL, 0)) {
      foreach (
$addresses as $address) {
       
$address = (array)$address;
       
$options[drupal_to_js($address)] = $address['street1'];
      }
    }
   
// inject into form
   
$form['panes']['delivery']['delivery_address_select']['#options'] = $options;
   
$form['panes']['billing']['billing_address_select']['#options'] = $options;
  }
}
?>

I've uploaded the files so you guys can test/improve this. Look forward to seeing what you come up with.

AttachmentSize
uc_addresses.tar.gz 10.42 KB
Joined: 02/06/2008
Juice: 200

would love to hear from someone who's using this mod. Garden ? It's snowing cows here !

Joined: 08/28/2007
Juice: 693

rolandk, please download, install and test out this module! Any and all testing is sorely needed.

I forgot to mention that addresses saved from the checkout process don't yet appear on the manage addresses page under user profile. I would say this needs to be tackled before it's ready for installation on a live site, otherwise users would probably get confused why they couldn't edit their saved addresses.

Joined: 09/20/2007
Juice: 36

Bump. Has anything been done on this project sense November? Very interested.

Joined: 08/28/2007
Juice: 693

Yes, some work has been done recently on this.

I've just noticed that the addresses are not showing in checkout for my new users. I'm going to be working on this for a bit to see if I can figure it out.

Joined: 08/28/2007
Juice: 693

Here's another version that works with users who don't have any addresses saved from checkout.

AttachmentSize
uc_addresses.module.txt 30.99 KB
Joined: 09/20/2007
Juice: 36
rich wrote:

Here's another version that works with users who don't have any addresses saved from checkout.

Thanks Rich! I'll play with it and see how it goes!