Multi-Step Form Code


/**
 * Important note:  product_list.php
 * is a required file that sets many variables here.
 * I could not include it here because I didn't have time to 
 * edit it - need to eliminate business-specific strings
 * and variables. Until I'm able to do a real tutorial with a
 * bogus content-type and business goal, there will be the 
 * added difficulty of not knowing what's in that include file.
 * Sorry...
 */

/**
 * Set a Menu item.
 */
function open_request_form_menu()
{
    $items["request_form/index2.php"] =
        array("title" => "Request Form",                        // Menu Item Text
              "page callback" => "open_request_form_generator", // Function that's run to generate the page when this menu item is hit.
              "access arguments" => array("access content"),
              "Description" => t("Request Form v0"),
              "menu_name" => "primary-links",
              "type" => MENU_NORMAL_ITEM);

    return $items;
}

/**
 * Implementation of hook_nodeapi
 * Email a notification of a Submitted request form.
 */
function open_request_form_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL)
{
    $request_email = "digital_requests@example.com,khalim.harris@example.com";
    if ($node->type=="production_request" && ($op=="update" || $op=="insert"))
    {
        $email_content = "The following request was ";
        if ($op == "update")
        {
            $email_content .= "updated:\n\n";
        }
        else
        if ($op == "insert")
        {
            $email_content .= "submitted:\n\n";
        }
        $email_content .= strtoupper($node->title) . ":\n";

        foreach ($node->field_desired_product as $idx=>$product)
        {
            $email_content .= "  " . $product["value"] . "\n";
        }

        $email_content .= "\nSTATUS: " . $node->field_project_status[0]["value"];
        $email_content .= "\n\n";
        $email_content .= $node->field_basecamp_requires[0]["value"];

        mail($node->field_email[0]["value"] . "," . $request_email, $node->nid . ": Production Request Updated", $email_content, "From: digital_request@example.com", "-f khalim.harris@example.com" );
    }
}

/**
 * Callback function for _menu hook (A wrapper fn).
 * Form Field Logic placed 
 */
function open_request_form_generator()
{
    return drupal_get_form("open_request_form");
}

/**
 * Multipage form building function.
 * This function typically returns a single form to be processed on first submit.
 * Instead, this one passes the responsibility to another function that checks
 * form_state and decides which page to return.  I left it this way to directly
 * show where the single- vs multi- page form separation occurs.
 * TODO:  Subsequent versions of this module should build elements
 * dynamically based on previous page values. Perhaps even build number of 
 * pages dynamically based on requirements.
 */
function open_request_form($form_state)
{
    return open_request_form_pages($form_state);
}

/*
 * Set up and display all pages.
 */
function open_request_form_pages($form_state)
{
    require "product_list.php";
    // Normally in a single-page form, this switch would be unnecessary. I'd simply build
    // the form array, display it, process it and build a node from it. 
    // Instead I MUST keep track of and update the "step" value in form_state. This is 
    // especially true in multi-step forms that require a "BACK" button.
    switch ($form_state["storage"]["step"])
    {
        case 2:
            foreach ($form_state["storage"]["page_vals"][1]["field_desired_product"] as $product=>$product_label)
            {
                if (empty($product_label))
                {
                    continue;
                }
        
                if (empty($product_hierarchy[$product]["prereqs"]) || $product_hierarchy[$product]["skip_in_form_build"] === true)
                {
                    continue;
                }

                $product_var = strtolower(str_replace(" ", "_", $product));
                $fieldset_name = $product_var . "_auto_form";
            
                $form[$fieldset_name] = array("#type" => "fieldset",
                                           "#title" => t($form_state["storage"]["page_vals"][1]["field_desired_product"][$product]),
                                           "#collapsible" => false,
                                           "#collapsed" => false);
            
                foreach ($product_hierarchy[$product]["prereqs"] as $rf_name=>$formtype)
                {
                    $this_field_name = "";

                    if ($formtype == "product_hierarchy_parent")
                    {
                        // If the desired product is recursive in definition (ie has a reference to parent: product_hierarchy_parent),
                        foreach ($product_hierarchy[$rf_name]["prereqs"] as $sub_rf_name=>$sub_formtype)
                        {
                            // Set form element name to a computer readable form field name.
            //                $this_field_name = "field_" . $product_var . "_" . strtolower(str_replace(" ", "_", $rf_name . "_$sub_rf_name"));
                            $this_field_name = "field_" . $cck_fields_prefix[$product] . $cck_fields_suffix[$sub_rf_name];
                            $form[$fieldset_name][$this_field_name] = $rf_types[$sub_formtype]; 
                            $form[$fieldset_name][$this_field_name]["#title"] = $product . " " . $sub_rf_name;
                            
                            // Set Defaults from form_state.
                            $form[$fieldset_name][$this_field_name]["#default_value"] = $form_state["values"][$this_field_name];
                            
                            // Sets Defaults and disables field when specific default set in config.
                            if (!empty($product_hierarchy[$rf_name]["defaults"][$sub_rf_name]))
                            {
                                $form[$fieldset_name][$this_field_name]["#default_value"] = $product_hierarchy[$rf_name]["defaults"][$sub_rf_name];
                                $form[$fieldset_name][$this_field_name]["#disabled"] = true;
                            }
                        }
                    }
                    else
                    {
                        // If no recursion in this definition
                        $this_field_name = "field_" . $cck_fields_prefix[$product] . (empty($cck_fields_suffix[$rf_name]) ? "_" . strtolower(str_replace(" ", "_", $rf_name)) : $cck_fields_suffix[$rf_name]);  // Product Name
                        $form[$fieldset_name][$this_field_name] = $rf_types[$formtype];
            
                        $form[$fieldset_name][$this_field_name]["#title"] = $product . " " . $rf_name;
            
                        // Sets Defaults and disables field when specific default set in config.
                        if (!empty($product_hierarchy[$product]["defaults"][$rf_name]))
                        {
                            $form[$fieldset_name][$this_field_name]["#default_value"] = $product_hierarchy[$product]["defaults"][$rf_name];
                            $form[$fieldset_name][$this_field_name]["#disabled"] = true;
                        }

                    }

                    // Sets Defaults when Form is Redrawn on error.
                    if (!empty($form_state["values"][$this_field_name]))
                    {
                        $form[$fieldset_name][$this_field_name]["#default_value"] = $form_state["values"][$this_field_name];
                    }
            
            
            // $form[$fieldset_name][$this_field_name] = $rf_types[$formtype];
                }
            }
            $form["back"] = array("#type" => "button",
                                  "#value" => "<< Back",
                                  "#executes_submit_callback" => true,
                                  "#submit" => array("my_rev_submit"));
            $form["next"] = array("#type" => "button",
                                  "#value" => "Review >>",
                                  "#executes_submit_callback" => true,
                                  "#submit" => array("my_submit"));

            break;
        case 3:
            $review_text = "";
            $review_text .= "Requestor's Name:\n" . 
                    $form_state["storage"]["page_vals"][1]["field_first_name"] . " " . 
                    $form_state["storage"]["page_vals"][1]["field_last_name"] . "\n\n";
            $review_text .= "E-mail:\n" . $form_state["storage"]["page_vals"][1]["field_email"] . "\n\n";
            $review_text .= "Dept:\n" . $form_state["storage"]["page_vals"][1]["field_department"] . "\n\n";
            $review_text .= "Phone:\n" . $form_state["storage"]["page_vals"][1]["field_phone"] . "\n\n";
            $review_text .= "Advertiser:\n" . $form_state["storage"]["page_vals"][1]["field_advertiser"] . "\n\n";
            $review_text .= "Project Name:\n" . $form_state["storage"]["page_vals"][1]["field_project_name"] . "\n\n";
        
            $review_text .= "PRODUCTS REQUESTED:\n";


// Building "Review Text" for last page of form - Just before form submission.
            foreach ($form_state["storage"]["page_vals"][1]["field_desired_product"] as $product=>$product_label)
            {
                if (empty($product_label))
                {
                    continue;
                }
        
                if (empty($product_hierarchy[$product]["prereqs"]) || $product_hierarchy[$product]["skip_in_form_build"] === true)
                {
                    continue;
                }

                $product_var = strtolower(str_replace(" ", "_", $product));

                if (!empty($form_state["storage"]["page_vals"][1]["field_desired_product"][$product]))
                {
                    $review_text .= "$product \n";
                }

                foreach ($product_hierarchy[$product]["prereqs"] as $rf_name=>$formtype)
                {
                    $this_field_name = "";

                    if ($formtype == "product_hierarchy_parent")
                    {
                        // If the desired product is recursive in definition (ie has a reference to parent: product_hierarchy_parent),
                        foreach ($product_hierarchy[$rf_name]["prereqs"] as $sub_rf_name=>$sub_formtype)
                        {
                            // Set form element name to a computer readable form field name.
                            $this_field_name = "field_" . $cck_fields_prefix[$product] . $cck_fields_suffix[$sub_rf_name];

                            $review_text .= "$sub_rf_name:  " . $form_state["storage"]["page_vals"][2][$this_field_name] . "\n";
                        }
                    }
                    else
                    {
                        // If no recursion in this definition
                        $this_field_name = "field_" . $cck_fields_prefix[$product] . (empty($cck_fields_suffix[$rf_name]) ? "_" . strtolower(str_replace(" ", "_", $rf_name)) : $cck_fields_suffix[$rf_name]);  // Product Name
                        if (is_array($form_state["storage"]["page_vals"][2][$this_field_name]))
                        {
                            $review_text .= "$rf_name: ";
                            foreach($form_state["storage"]["page_vals"][2][$this_field_name] as $akey=>$aval)
                            {
                                $review_text .= empty($aval) ? "" : "$aval ";
                            }
                            $review_text .= "\n";
                        }
                        else
                        {
                            $review_text .= "$rf_name:  " . $form_state["storage"]["page_vals"][2][$this_field_name] . "\n";
                        }
                    }
                }
            }
            
            $form["your_submission"] = array("#type" => "fieldset",
                                            "#title" => t("You are about to submit the following:"),
                                            "#collapsible" => true,
                                            "#collapsed" => false);
        
            $form["your_submission"]["text_version"] = array("#type" => "textarea",
                                                             "#title" => t("Submission"),
                                                             "#rows" => 20,
                                                             "#disabled" => true,
                                                             "#default_value" => $review_text);
            $form["back"] = array("#type" => "button",
                                  "#value" => "<< Back",
                                  "#executes_submit_callback" => true,
                                  "#submit" => array("my_rev_submit"));

            $form["next"] = array("#type" => "submit",
                                  "#value" => "Submit");
            break;
        
        // PAGE 1... Contact info and Products desired.
        case 1:
        default:
            $form["contact_info"] = array("#type" => "fieldset",
                                          "#title" => t("Contact Info"),
                                          "#collapsible" => true,
                                          "#collapsed" => false);
            $form["contact_info"]["field_first_name"] = array("#type" => "textfield",
                                          "#title" => t("First Name"),
                                          "#required" => true,
                                          "#default_value" => isset($form_state["values"]["field_first_name"]) ? $form_state["values"]["field_first_name"] : "");
            $form["contact_info"]["field_last_name"] = array("#type" => "textfield",
                                          "#title" => t("Last Name"),
                                          "#required" => true,
                                          "#default_value" => isset($form_state["values"]["field_last_name"]) ? $form_state["values"]["field_last_name"] : "");
            $form["contact_info"]["field_email"] = array("#type" => "textfield",
                                          "#title" => t("Email"),
                                          "#required" => true,
                                          "#default_value" => isset($form_state["values"]["field_email"]) ? $form_state["values"]["field_email"] : "");
            $form["contact_info"]["field_department"] = array("#type" => "textfield",
                                          "#title" => t("Department"),
                                          "#required" => true,
                                          "#default_value" => isset($form_state["values"]["field_department"]) ? $form_state["values"]["field_department"] : "");
            $form["contact_info"]["field_phone"] = array("#type" => "textfield",
                                          "#title" => t("Phone Number"),
                                          "#required" => true,
                                          "#default_value" => isset($form_state["values"]["field_phone"]) ? $form_state["values"]["field_phone"] : "");
            $form["contact_info"]["field_advertiser"] = array("#type" => "textfield",
                                          "#title" => t("Advertiser"),
                                          "#required" => true,
                                          "#default_value" => isset($form_state["values"]["field_advertiser"])? $form_state["values"]["field_advertiser"] : "");
            $form["contact_info"]["field_project_name"] = array("#type" => "textfield",
                                          "#title" => t("Project Name"),
                                          "#required" => true,
                                          "#default_value" => isset($form_state["values"]["field_project_name"]) ? $form_state["values"]["field_project_name"] : "");

            $form["about"] = array("#type" => "fieldset",
                                   "#title" => t("About Campaign"),
                                   "#collapsible" => true,
                                   "#collapsed" => false);
            $form["about"]["field_request_type"] = array("#type" => "radios",
                                                         "#required" => true,
                                                         "#options" => $request_types,
                                                         "#default_value" => empty($form_state["values"]["field_request_type"]) ? "Example.com" : 
                                                                                   $form_state["values"]["field_request_type"]);
            $form["about"]["field_advert_approval"] = array("#type" => "select",
                                                            "#required" => true,
                                                            "#options" => $advert_approval_requirement,
                                                            "#default_value" => empty($form_state["values"]["field_advert_approval"]) ? "Not Required" :
                                                                                      $form_state["values"]["field_advert_approval"]);
            $form["about"]["field_internal_approval"] = array("#type" => "select",
                                                            "#required" => true,
                                                            "#options" => $internal_approval_requirement,
                                                            "#default_value" => empty($form_state["values"]["field_internal_approval"]) ? "Not Required" :
                                                                                      $form_state["values"]["field_internal_approval"]);


            $form["product"] = array("#type" => "fieldset",
                                          "#title" => t("Our Products"),
                                          "#collapsible" => true,
                                          "#collapsed" => false);
            $form["product"]["field_desired_product"] = array("#type" => "checkboxes",
                                             "#title" => t("What do you need?"),
                                             "#required" => true,
                                             "#options" => $product_select_list,
                                             "#default_value" => empty($form_state["values"]["field_desired_product"]) ? array() : 
                                                                       $form_state["values"]["field_desired_product"]);

            $form["next"] = array("#type" => "button",
                                  "#value" => "Next >>",
                                  "#executes_submit_callback" => true,
                                  "#submit" => array("my_submit"));   // Interrupt the normal flow and do my own SUBMIT process.

            break;
    }
    return $form;
}

/**
 * My Own SUBMIT process. Each normal submit operation that's "interrupted" is 
 * run through this function (all but the last one... which does the normal form_submit hook).
 */
function my_submit($form, &$form_state)
{
        $form_state["rebuild"] = true;

        $form_state["storage"]["page_vals"][$form_state["storage"]["step"]] = $form_state["values"];
        $form_state["values"] = $form_state["storage"]["page_vals"][$form_state["storage"]["step"] + 1];
        $form_state["storage"]["step"]++;
}

/**
 * Implementation of hook_form_validate
 * I set messages and errors here after confirming non-empty states
 * and accepted values.
 */
function open_request_form_validate($form, &$form_state)
{
    require "product_list.php";
    if ($form_state["clicked_button"]["#value"] == "<< Back")
    {
        $form_state["rebuild"] = true;
        $form_state["storage"]["page_vals"][$form_state["storage"]["step"]] = $form_state["values"];
        $form_state["values"] = $form_state["storage"]["page_vals"][$form_state["storage"]["step"] - 1];
        $form_state["storage"]["step"]--;
        return true;
    }

    switch ($form_state["storage"]["step"])
    {
        case "2" :
            foreach ($cck_fields_prefix as $label => $var_name)
            {
                if (in_array("field_" . $var_name . "_ld", array_keys($form_state["values"])) &&
                    empty($form_state["values"]["field_" . $var_name . "_ld"]))
                {
                    form_set_error("field_" . $var_name . "_ld", "$label Launch Date: This field is required");
                }

                if (in_array("field_" . $var_name . "_ed", array_keys($form_state["values"])) &&
                    empty($form_state["values"]["field_" . $var_name . "_ed"]))
                {
                    form_set_error("field_" . $var_name . "_ed", "$label Launch Date: This field is required");
                }

                //  If both launch and end dates are set AND launch happens after end, set "wrong order" error message.
                if ((isset($form_state["values"]["field_" . $var_name . "_ld"]) &&
                    isset($form_state["values"]["field_" . $var_name . "_ed"])) &&
                    ($form_state["values"]["field_" . $var_name . "_ld"] > $form_state["values"]["field_" . $var_name . "_ed"]))
                {
                    form_set_error("field_" . $var_name . "_ed", "$label: End Date is BEFORE Launch Date");
                }
            }
            break;
        case "3" :
            if ($form_state["clicked_button"]["#value"] == "<< Back")
            {
                $form_state["rebuild"] = true;
                $form_state["storage"]["page_vals"][$form_state["storage"]["step"]] = $form_state["values"];
                $form_state["values"] = $form_state["storage"]["page_vals"][$form_state["storage"]["step"] - 1];
                $form_state["storage"]["step"]--;
                return true;
            }
            $form_state["storage"]["step"] = "3";
            break;
        case "4" :
            break;
        default:
            $form_state["storage"]["step"] = 1;
            break;
    }
    return;
}

/**
 * hook_form_submit function
 * Do final value checking and save form values as a node.
 * I have found that the various form elements are stored
 * and read in different ways from the form_state variable.
 * Pay close attention to form_state as the differences are somewhat astounding.
 * Also, some values are OK to not be previously set.. but others MUST be set 
 * to empty string "" to be useful at any level.  For this project, it was 
 * imperative to set all date strings to "".  
 * Checkboxes and radio buttons are different too.
 * Plain textboxes are the easiest. So I used those a lot.
 */
function open_request_form_submit($form, &$form_state)
{
    require "product_list.php";

    if ($form_state["storage"]["step"] < "3")
    {
        $form_state["rebuild"] = true;
        $form_state["storage"]["values"] = $form_state["values"];
        $form_state["storage"]["page_vals"][$form_state["storage"]["step"]] = $form_state["values"];
        $form_state["storage"]["step"]++;
    }
    else
    {
        foreach ($form_state["storage"]["page_vals"] as $key=>$val)
        {
//            $new_node;
        }

    /**
     * Used to fix Erroneous dates from other functions that CAN be interpretted by strtotime.
     */
    function fix_date($date_pop_str)
    {
        if(!empty($date_pop_str))
        {
            return date("Y-m-d", strtotime($date_pop_str));
        }
        return "";
    }


// create $form_state
// $form_state = array();

// include node file, necessary for node generation
module_load_include('inc', 'node', 'node.pages');
// create $node
$node = array("type" => "production_request");
$form_state['values']["title"] = $form_state["storage"]["page_vals"][1]["field_project_name"];
$form_state['values']["body"] = "what body";
$form_state['values']["name"] = "tools_form_submitter";
$form_state['values']["op"] = t("Save");


// add CCK fields
/*
$form_state['values']['field_first_name'][0]['value'] = $form_state["storage"]["page_vals"][1]["field_first_name"];
$form_state['values']['field_last_name'][0]['value'] = $form_state["storage"]["page_vals"][1]["field_last_name"];
$form_state['values']['field_email'][0]['value'] = $form_state["storage"]["page_vals"][1]["field_email"];
$form_state['values']['field_department'][0]['value'] = $form_state["storage"]["page_vals"][1]["field_department"];
$form_state['values']['field_phone'][0]['value'] = $form_state["storage"]["page_vals"][1]["field_phone"];
$form_state['values']['field_advertiser'][0]['value'] = $form_state["storage"]["page_vals"][1]["field_advertiser"];
$form_state['values']['field_project_name'][0]['value'] = $form_state["storage"]["page_vals"][1]["field_project_name"];
$form_state['values']['field_desired_product']['value']= $form_state["storage"]["page_vals"][1]["field_desired_product"];
*/

// Hardcoding setting of some fields that are not originally part of the form
// (eliminated by "What you need" on the first page of the form).
foreach ($cck_fields_prefix as $label=>$field_name)
{
// print "$field_name\n";
    $form_state['values']['field_' . $field_name][0]["value"] = "";
    $form_state['values']['field_' . $field_name . '_ld'][0]["value"]["date"] = null;
    $form_state['values']['field_' . $field_name . '_ed'][0]["value"]["date"] = null;
    $form_state['values']['field_' . $field_name]["value"] = "";
}

$form_state["values"]["field_adzone_sizes"]["value"] = array();
$form_state["values"]["field_cib_size"]["value"] = array();
$form_state["values"]["field_screenshots_format"]["value"] = "jpg";
// $form_state['values']['field_internal_approval']['value']['value'] = "Not Required";
// $form_state['values']['field_advert_approval']['value']['value'] = "Not Required";
// $form_state['values']['field_request_type']['value'] = "Example.com";

foreach ($form_state["storage"]["page_vals"] as $step => $current_form)
{
    foreach ($current_form as $page_key=>$page_val)
    {
        // If it's not a "field" skip it.
        if (strpos($page_key, "field_") !== 0)
        {
            continue;
        }

        // if it's a date field - which ends in _ld or _ed
        if (preg_match("/_ed$/", $page_key) || preg_match("/_ld$/", $page_key))
        {
            if (!empty($page_val))
            {
                $form_state["values"][$page_key][0]["value"]["date"] = fix_date($page_val);
            }
            else
            {
                print "SOME ERROR 1 - A Date... $page_key, $page_val";
            }
        }
        //  It's not a date field.  It's text.
        else
        {
//print "$step, $page_key, $page_val";
            if (isset($page_val))
            {
$var_inspect = array("field_request_type","field_advert_approval","field_internal_approval");
/*if (in_array($page_key, $var_inspect))
{
print "
";
print "$page_key:";
print_r($page_val);    
print "

";
}
*/
if (is_array($page_val))
{
$form_state["values"][$page_key]["value"] = $page_val;
}
else
{
// WHAT?! Some text (I thinktext boxes and single select boxes?) fields are set like this...
$form_state["values"][$page_key][0]["value"] = $page_val;
// WHAT?! Other text (I think select lists and radios) fields are set like this....
$form_state["values"][$page_key]["value"] = $page_val;
}
// print_r($page_val);
// print "\n";
}
else
{
print "SOME ERROR 2 - A Date... $page_key, $page_val";
}
}
}
}

// Set all Desired Product Values to empty...
// This prevents Extras showing up on the page set to "0" - string zero.
foreach ($form_state["values"]["field_desired_product"]["value"] as $key=>$val)
{
if (empty($form_state["values"]["field_desired_product"]["value"][$key]))
{
$form_state["values"]["field_desired_product"]["value"][$key] = "";
}
}

$form_state["values"]["field_critical_timestamp"][0]["value"] = strtotime(date("Y-m-d", $soonest_due_timestamp) . " - 7 days");

//print_r($form_state["values"]);

$form_state["values"]["field_basecamp_requires"][0]["value"] = "";
// Set requirements for Basecamp Creation..
foreach ($form_state["storage"]["page_vals"][1]["field_desired_product"] as $product => $wanted)
{
if (!empty($wanted))
{
$form_state["values"]["field_basecamp_requires"][0]["value"] .= "For $product:\n";
$form_state["values"]["field_basecamp_requires"][0]["value"] .= implode("\n", isset($product_requirements[$product]) ? $product_requirements[$product] : array()) . "\n\n";
}
}

// Date Values: [field_launch_date] => Array ( [0] => Array ( [value] => 2009-09-01T00:00:00 [timezone] => America/New_York [timezone_db] => America/New_York [date_type] => date [view] => 2009-09-01 ) )

// create node using drupal_execute
drupal_execute('production_request_node_form', $form_state, (object) $node);

/*
$form_state = array();
module_load_include('inc', 'node', 'node.pages');
$node = array('type' => 'story');
$form_state['values']['title'] = 'My node';
$form_state['values']['body'] = 'This is the body text!';
$form_state['values']['name'] = 'admin';
$form_state['values']['op'] = t('Save');
$form_state["values"]["field_middle_name"][0]["value"] = "A MIDDLE name";
drupal_execute('story_node_form', $form_state, (object)$node);
*/

drupal_set_message("

THANK YOU FOR YOUR ENTRY.  

");

// VERY IMPORTANT
unset ($form_state["storage"]); // VERY IMPORTANT

$form_state["redirect"] = "node/" . $form_state["nid"];

}
}