Extracting fields from user registration form and adding to checkout form?

Posts: 61
Joined: 05/05/2008
Bug Finder

The user registration form on my site has many custom fields. I need to get some of these fields on to the checkout form within ubercart. Creating a pane within the checkout page is not a problem, but what's the best way to populate it?

I could do a manual pull from the database and manually create new form elements, then validate them myself, then process them... I'm wondering, is there an easier (or more "proper") way?

Thanks!

Posts: 61
Joined: 05/05/2008
Bug Finder

(Ubercart RC4)

OK I must be missing something obvious here...

I've got the form fields I want on the checkout page, but when the form is submitted error messages come up saying that the required fields (within the new set of fields I created) are not filled out, even though they are.

I print out $arg1 and $arg2 from within the PROCESS case of uc_checkout_pane_profile() (callback for uc_profile_checkout_pane()), and get this: $arg1 contains all of ubercart's fields (properly filled out), while $arg2 contains all of my fields, and they're all empty...

Here is the module code:

<?php
/*******************************************************************************
* Hook Functions (Ubercart)
******************************************************************************/

/**
* Implementation of hook_checkout_pane().
*/
function uc_profile_checkout_pane()
{
   
$panes[] = array(
       
'id' => 'profile',
       
'callback' => 'uc_checkout_pane_profile',
       
'title' => t('Profile Information'),
       
'desc' => t("Display additional profile fields."),
       
'weight' => 20,
       
'process' => TRUE,
       
'collapsible' => TRUE,
    );

    return
$panes;
}

/*******************************************************************************
* Callback Functions, Forms, and Tables
******************************************************************************/

/**
* Callback for uc_profile_checkout_pane()
*/   
function uc_checkout_pane_profile($op, &$arg1, $arg2)
{
    switch (
$op)
    {
        case
'view':
           
$description = t('Please fill out your profile information!');
           
           
$contents = uc_profile_checkout_form();
                       
            return array(
'contents' => $contents, 'description' => $description);

        case
'process':
       
            echo
'uc_checkout_pane_profile() -> PROCESS case';
           
print_rr($arg1);
           
print_rr($arg2);       
                   
            return
TRUE;

        case
'review':           
           
$review = 'test123';
            return
$review;
    }
}

/**
* Generates the form displayed on the checkout page
*/   
function uc_profile_checkout_form()
{
   
// tons of junk which generates the form here    
   
return $form;
}
?>

Can anyone shed some light?

//edit
According to http://www.ubercart.org/docs/developer/245/checkout, I "must implement hook_order() to save/load information collected in a pane." So, I guess I have to save the information within hook_order(), and then display it (load case) for the review page?

//edit2
Does't look like any of my custom fields are available to hook_order in the SAVE case :/...

Posts: 61
Joined: 05/05/2008
Bug Finder

Looks like the problem is with my form items... something there is preventing the values from going through (works with a test-form of just one textfield and select).

Posts: 4368
Joined: 08/07/2007
AdministratorHead Code Monkey - I eat bugs.

I tried an integration before where I added profile fields specified as necessary for registration into the customer information pane. I let it go b/c I didn't have the time to nurse it all the way to completion.

I like your idea of making it a separate checkout pane. I'm not exactly sure what the problem might be based on your posts so far, but if you can post up a zip of the code I'd be happy to give a quick scan.

Posts: 61
Joined: 05/05/2008
Bug Finder

Hey Ryan,

I actually got it mostly working. All that's left is to save the data in the user's profile, which I'll take care of tomorrow morning (I'm thinking of trying it with drupal_execute() with the edit-user form). I'll post the module I wrote for it - it's quite specific to my particular needs (individual fields and such), but might be helpful to anyone doing something similar. Look for it tomorrow Smiling

Posts: 61
Joined: 05/05/2008
Bug Finder

Got a few emails from people asking to see the code, so here is what I got...

This requires a small addition to the uc_orders table:
- Add a field called 'profile_values' of type 'blob'.
(Ideally this should be separated into a new table, but I just did it the quick and dirty way.)

What this (uc_profile) module does:
- Pulls up all data from the user registration form (within uc_profile_checkout_form() )
- *** Removes certain fields
- *** Reorders certain fields
- *** Makes some field sets collapsible
- Upon clicking the 'review order' button the profile data is serialized and stored in uc_orders.profile_values (done in uc_checkout_pane_profile(), process case)
- Upon landing on the review order page (uc_checkout_pane_profile(), review case), the serialized data is pulled and passed to the theme function uc_profile_table()
- *** Data is themed by the theme function
- Upon clicking the 'submit order' button the order gets processed and new user created
- uc_profile_user() is called with the case 'insert,' and the submitted profile data is pushed to the profile_values table for the appropriate UID

*** = specific to my needs and will likely need to be altered

(Sorry for the scrolling - posting in-line for the benefit of search engines and people too lazy to click the download link - you know who you are Eye-wink.)

<?php
/*******************************************************************************
* Hook Functions (Ubercart)
******************************************************************************/

/**
* Implementation of hook_checkout_pane().
*/
function uc_profile_checkout_pane()
{
   
$panes[] = array(
       
'id' => 'profile',
       
'callback' => 'uc_checkout_pane_profile',
       
'title' => t('Profile Information'),
       
'desc' => t("Display additional profile fields."),
       
'weight' => 10,
       
'process' => TRUE,
       
'collapsible' => TRUE,
    );

    return
$panes;
}

function
uc_profile_user($op, &$edit, &$account, $category = NULL)
{
    switch(
$op)
    {
       
/**
         * User account is being created. At this point we already have
         * the user object in $account, and need to populate the profile
         * fields with the data stored in the order.
         */       
       
case 'insert':
               
           
/**
             * Let's load the submitted profile fields from the database.
             * We use the user's email to find the row. Emails are unique,
             * and since the user is just registering this account this
             * will be the user's first (and only) order.
             */
                       
            //TODO: Possible freak case: user registers and makes an order,
            //account is deleted, user re-registers with the same email
            //as before and makes an order. This could result in multiple
            //orders with the same email address.                        
           
$mail = $account->mail;           
           
$profile = unserialize(db_fetch_object(db_query("SELECT profile_fields FROM {uc_orders} WHERE primary_email='%s'", $mail))->profile_fields);           
           
           
/**
             * Array of fields which will be updated in the {profile_values} table.
             * The key is the 'name' field from {profile_fields}
             */
            
            //TODO: Streamline/automate this process.
            //Store the profile_field.value in the same array from the start?
           
$fields = array(
               
'profile_silverpop' => $profile['Join mailing list'],
               
'profile_search_privacy' => $profile['Show profile in search results'],
               
'profile_general_interest' => $profile['No children'],
               
'profile_babyname' => $profile['First Child']["Child's name"],
               
'profile_babydate' => $profile['First Child']['Birth year'].$profile['First Child']['Birth month'].$profile['First Child']['Birth day'],
               
'profile_babygender' => $profile['First Child']['Gender'],   
                           
               
'profile_babyname2' => $profile['Second Child']["Child's name"],
               
'profile_babydate2' => $profile['Second Child']['Birth year'].$profile['Second Child']['Birth month'].$profile['Second Child']['Birth day'],
               
'profile_babygender2' => $profile['Second Child']['Gender'],   
                           
               
'profile_babyname3' => $profile['Third Child']["Child's name"],
               
'profile_babydate3' => $profile['Third Child']['Birth year'].$profile['Third Child']['Birth month'].$profile['Third Child']['Birth day'],
               
'profile_babygender3' => $profile['Third Child']['Gender'],   
                           
               
'profile_babyname4' => $profile['Fourth Child']["Child's name"],
               
'profile_babydate4' => $profile['Fourth Child']['Birth year'].$profile['Fourth Child']['Birth month'].$profile['Fourth Child']['Birth day'],
               
'profile_babygender4' => $profile['Fourth Child']['Gender'],
               
               
'profile_priv_policy' => 1,
            );       
                       
           
$uid = $account->uid;
           
           
/**
             * The fields already exist with empty values, so all we need to do is
             * update them
             */
           
foreach($fields as $key => $field)
            {
               
$query = "    UPDATE    {profile_values} pv, {profile_fields} pf
                            SET        pv.value = '%s'
                            WHERE    pf.name = '%s'
                            AND        pf.fid = pv.fid
                            AND        pv.uid = '%d'                           
                         "
;
                        
               
db_query($query, $field, $key, $uid);   
            }
                           
            break;   
    }
}

/*******************************************************************************
* Callback Functions, Forms, and Tables
******************************************************************************/

/**
* Callback for uc_profile_checkout_pane()
*/   
function uc_checkout_pane_profile($op, &$arg1, $arg2)
{
    switch (
$op)
    {
        case
'view':
           
$description = t('Please fill out your profile information!');
           
           
$contents = uc_profile_checkout_form();
                       
            return array(
'contents' => $contents, 'description' => $description);

        case
'process':   
           
/**
             * Array holding the submitted data in a custom (cleaner) format.
             * The keys are renamed to provide the user with nicer titles on
             * the order-review page.
             */       
           
$profile = array();
           
$profile['Join mailing list'] = $arg2['Stay Informed']['profile_silverpop'];
           
$profile['Show profile in search results'] = $arg2['Privacy']['profile_search_privacy'];
           
$profile['No children'] = $arg2['Children']['profile_general_interest'];
           
$profile['First Child'] = array(
               
'Child\'s name' => $arg2['First Child']['profile_babyname'],
               
'Birth month' => $arg2['First Child']['profile_babydate']['month'],
               
'Birth day' => $arg2['First Child']['profile_babydate']['day'],
               
'Birth year' => $arg2['First Child']['profile_babydate']['year'],
               
'Gender' => $arg2['First Child']['profile_babygender']
            );
           
$profile['Second Child'] = array(
               
'Child\'s name' => $arg2['Second Child']['profile_babyname2'],
               
'Birth month' => $arg2['Second Child']['profile_babydate2']['month'],
               
'Birth day' => $arg2['Second Child']['profile_babydate2']['day'],
               
'Birth year' => $arg2['Second Child']['profile_babydate2']['year'],
               
'Gender' => $arg2['Second Child']['profile_babygender2']
            );
           
$profile['Third Child'] = array(
               
'Child\'s name' => $arg2['Third Child']['profile_babyname3'],
               
'Birth month' => $arg2['Third Child']['profile_babydate3']['month'],
               
'Birth day' => $arg2['Third Child']['profile_babydate3']['day'],
               
'Birth year' => $arg2['Third Child']['profile_babydate3']['year'],
               
'Gender' => $arg2['Third Child']['profile_babygender3']
            );
           
$profile['Fourth Child'] = array(
               
'Child\'s name' => $arg2['Fourth Child']['profile_babyname4'],
               
'Birth month' => $arg2['Fourth Child']['profile_babydate4']['month'],
               
'Birth day' => $arg2['Fourth Child']['profile_babydate4']['day'],
               
'Birth year' => $arg2['Fourth Child']['profile_babydate4']['year'],
               
'Gender' => $arg2['Fourth Child']['profile_babygender4']
            );
           
           
$profile = serialize($profile);
           
           
/**
             * Store the serialized profile data with the order
             */
            //TODO: Separate into antother table with fields 'order_id' and 'profile_filds'
           
db_query("UPDATE {uc_orders} SET profile_fields='%s' WHERE order_id='%d' LIMIT 1", $profile, $arg1->order_id);
                           
            return
TRUE;

        case
'review':
           
/**
             * Retrieve the profile data stored with the order and pass it to the theme function.
             */           
           
$profile = unserialize(db_fetch_object(db_query("SELECT profile_fields FROM {uc_orders} WHERE order_id='%d'", $arg1->order_id))->profile_fields);           
       
            return array(
theme('uc_profile_table', $profile));
    }
}

/**
* Generates the form displayed on the checkout page
*/   
function uc_profile_checkout_form()
{
   
$form = array();
   
   
//if only I knew about this function *before* spending 5 hours
    //on tricky ways to pull and re-construct the form manually...
   
$form = drupal_retrieve_form('user_register');
   
   
//remove unnecessary fields
   
unset($form['user_registration_help']);
    unset(
$form['account']);
    unset(
$form['About You']);
    unset(
$form['submit']);
    unset(
$form['#parameters']);
   
   
//reorder fields
   
$form['Children']['#weight']         = 0;
   
$form['First Child']['#weight']     = 1;
   
$form['Second Child']['#weight']     = 2;
   
$form['Third Child']['#weight']     = 3;
   
$form['Fourth Child']['#weight']     = 4;
   
$form['Stay Informed']['#weight']     = 5;
   
$form['Privacy']['#weight']         = 6;
   
   
//make fieldsets collapsible
   
$form['Second Child']['#collapsible']    = 1;
   
$form['Second Child']['#collapsed']     = 1;
   
$form['Third Child']['#collapsible']    = 1;
   
$form['Third Child']['#collapsed']         = 1;
   
$form['Fourth Child']['#collapsible']    = 1;
   
$form['Fourth Child']['#collapsed']     = 1;
       
    return
$form;
}

/**
* Overridable theme function to output the submitted profile information
* on the order review screen
*/
function theme_uc_profile_table($data)
{
   
/**
     * Track the depth of table nesting in order to remove
     * td-classes from all tables except the exterior one
     */    
   
static $depth;
   
    if(
is_null($depth))
       
$depth = 1;
    else
       
$depth++;
   
   
$output = '<table class="order-review-profile-table depth-'.$depth.'">';
   
    foreach(
$data as $title => $value)
    {
       
$output .= '<tr valign="top">';
       
       
//only add the classes to the exterior table's TDs
       
$class1 = ($depth == 1) ? 'title-col' : '';
       
$class2 = ($depth == 1) ? 'data-col'  : '';
   
       
/**
         * Don't output anything if the field's value is empty, or if
         * the Gender field's value is 0. NOTE: 0 is a string, so it
         * must be enclosed in quotes.
         */
       
if($value != '' && !(strtolower($title) == 'gender' && $value == '0'))
        {   
            if(
is_array($value))
            {
               
/**
                 * Check to see if the child array has any data in the value
                 * fields. If not, we don't need to output anything.
                 */
               
$dig_deeper = check_empty_array($value);
               
                if(
$dig_deeper)   
                {
                   
$output .= '<td class="sub-heading" colspan="2">'.$title.theme_uc_profile_table($value).'</td>';
                }
            }
            else
            {
               
$output .= '<td class="'.$class1.'">'.$title.':</td>';           
               
$output .= '<td class="'.$class2.'">'.$value.'</td>';   
            }
        }
       
       
$output .= '</tr>';   
    }
   
   
$output .= '</table>';
   
   
$depth--;
    return
$output;   
}

/**
* Checks array values (and sub-array values) to see if there's
* any data, or if all keys contain empty values
*/
function check_empty_array($data)
{
   
//TODO: Double check functionality with arrays 3+ levels deep and clean up function
   
$dig_deeper = false;
    if(
is_array($data) && sizeof($data) > 0)
    {
        foreach(
$data as $key => $value)
        {
            if(
is_array($value))
            {
               
$dig_deeper = check_empty_array($value);
               
                if(
$dig_deeper)
                {
                   
//there are non-empty values within the array - that's all we need to know
                   
break;
                }
            }   
            else
            {
                if(!empty(
$value))
                {
                   
$dig_deeper = true;
                    break;                   
                }                   
            }
        }
    }

    return
$dig_deeper;
}
?>

Hopefully this proves useful to someone.
Maybe I'll make a proper module out of this one day...

IMPORTANT: this was developed on/for RC4 of Ubercart and Drupal 5.7. NOT tested on any other configuration.

Posts: 12
Joined: 08/09/2007

Thanks for this code--it's great!!

Quote:

(Sorry for the scrolling - posting in-line for the benefit of search engines and people too lazy to click the download link - you know who you are Eye-wink .)

Guilty as accused, heh heh. Smiling