The code

Posts: 67
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.