dslax27 Posted March 18, 2008 Share Posted March 18, 2008 Hello! Complete noob here, but I did my homework before posting so don't yell at me too much please haha! My Goal: Create a simple multi-step form that both saves the data in a database and E-mails me a copy. What I've gathered so far: According to http://www.phpfreaks.com/forums/index.php/topic,119933.0.html it seems the best route for a multi-step (or multi-page) form is to use temporary tables in my database instead of extending the url as the user moves along. I found a solid example here: http://www.phpriot.com/articles/multi-step-wizards/5 Front End: <?php require_once('CheckoutWizard.class.php'); $wizard = new CheckoutWizard(); $action = $wizard->coalesce($_GET['action']); $wizard->process($action, $_POST, $_SERVER['REQUEST_METHOD'] == 'POST'); // only processes the form if it was posted. this way, we // can allow people to refresh the page without resubmitting // form data ?> <html> <head> <title>phpRiot() wizard example</title> </head> <body> <h1>phpRiot() wizard example</h1> <?php if ($wizard->isComplete()) { ?> <p> The form is now complete. Clicking the button below will clear the container and start again. </p> <form method="post" action="<?= $_SERVER['PHP_SELF'] ?>?action=<?= $wizard->resetAction ?>"> <input type="submit" value="Start again" /> </form> <?php } else { ?> <form method="post" action="<?= $_SERVER['PHP_SELF'] ?>?action=<?= $wizard->getStepName() ?>"> <h2><?= $wizard->getStepProperty('title') ?></h2> <?php if ($wizard->getStepName() == 'userdetails') { ?> <table> <tr> <td>Name:</td> <td> <input type="text" name="name" value="<?= htmlSpecialChars($wizard->getValue('name')) ?>" /> </td> <td> <?php if ($wizard->isError('name')) { ?> <?= $wizard->getError('name') ?> <?php } ?> </td> </tr> <tr> <td>Email:</td> <td> <input type="text" name="email" value="<?= htmlSpecialChars($wizard->getValue('email')) ?>" /> </td> <td> <?php if ($wizard->isError('email')) { ?> <?= $wizard->getError('email') ?> <?php } ?> </td> </tr> <tr> <td>Country:</td> <td> <select name="country"> <option value=""></option> <?php foreach ($wizard->countries as $k => $v) { ?> <option value="<?= $k ?>"<?php if ($wizard->getValue('country') == $k) { ?> selected="selected"<?php } ?>> <?= $v ?> </option> <?php } ?> </select> </td> <td> <?php if ($wizard->isError('country')) { ?> <?= $wizard->getError('country') ?> <?php } ?> </td> </tr> </table> <?php } else if ($wizard->getStepName() == 'billingdetails') { ?> <table> <tr> <td>Credit Card Type:</td> <td> <select name="cc_type"> <option value=""></option> <?php foreach ($wizard->ccTypes as $v) { ?> <option value="<?= $v ?>"<?php if ($wizard->getValue('cc_type') == $v) { ?> selected="selected"<?php } ?>> <?= $v ?> </option> <?php } ?> </select> </td> <td> <?php if ($wizard->isError('cc_type')) { ?> <?= $wizard->getError('cc_type') ?> <?php } ?> </td> </tr> <tr> <td>Credit Card Number:</td> <td> <input type="text" name="cc_number" value="<?= htmlSpecialChars($wizard->getValue('cc_number')) ?>" /> </td> <td> <?php if ($wizard->isError('cc_number')) { ?> <?= $wizard->getError('cc_number') ?> <?php } ?> </td> </tr> </table> <?php } else if ($wizard->getStepName() == 'confirm') { ?> <p> Please verify the entered details and then click next to complete your order. </p> <table> <tr> <td>Name:</td> <td><?= $wizard->getValue('name') ?></td> </tr> <tr> <td>Email:</td> <td><?= $wizard->getValue('email') ?></td> </tr> <tr> <td>Credit Card Type:</td> <td><?= $wizard->getValue('cc_type') ?></td> </tr> <tr> <td>Credit Card Number:</td> <td><?= $wizard->getValue('cc_number') ?></td> </tr> </table> <?php } ?> <p> <input type="submit" name="previous" value="<< Previous"<?php if ($wizard->isFirstStep()) { ?> /> <input type="submit" value="<?= $wizard->isLastStep() ? 'Finish' : 'Next' ?> >>" /> </p> </form> <?php } ?> </body> </html> Back End: <?php /** * Copyright 2005 Zervaas Enterprises (www.zervaas.com.au) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * ZervWizard * * A class to manage multi-step forms or wizards. This involves managing * the various steps, storing its values and switching between each * step * * @author Quentin Zervaas */ class ZervWizard { // whether or not all steps of the form are complete var $_complete = false; // internal array to store the various steps var $_steps = array(); // the current step var $_currentStep = null; // the prefix of the container key where form values are stored var $_containerPrefix = '__wiz_'; // an array of any errors that have occurred var $_errors = array(); // key in container where step status is stored var $_step_status_key = '__step_complete'; // key in container where expected action is stored var $_step_expected_key = '__expected_action'; // options to use for the wizard var $options = array('redirectAfterPost' => true); // action that resets the container var $resetAction = '__reset'; /** * ZervWizard * * Constructor. Primarily sets up the container * * @param array &$container Reference to container array * @param string $name A unique name for the wizard for container storage */ function ZervWizard(&$container, $name) { if (!is_array($container)) { $this->addError('container', 'Container not valid'); return; } $containerKey = $this->_containerPrefix . $name; if (!array_key_exists($containerKey, $container)) $container[$containerKey] = array(); $this->container = &$container[$containerKey]; if (!array_key_exists('_errors', $this->container)) $this->container['_errors'] = array(); $this->_errors = &$this->container['_errors']; } /** * process * * Processes the form for the specified step. If the processed step * is complete, then the wizard is set to use the next step. If this * is the initial call to process, then the wizard is set to use the * first step. Once the next step is determined, the prepare method * is called for the step. This has the method name prepare_[step name]() * * @todo Need a way to jump between steps, e.g. from step 2 to 4 and validating all data * @param string $action The step being processed. This should correspond * to a step created in addStep() * @param array &$form The unmodified form values to process * @param bool $process True if the step is being processed, false if being prepared */ function process($action, &$form, $process = true) { if ($action == $this->resetAction) { $this->clearContainer(); $this->setCurrentStep($this->getFirstIncompleteStep()); } else if (isset($form['previous']) && !$this->isFirstStep()) { // clear out errors $this->_errors = array(); $this->setCurrentStep($this->getPreviousStep($action)); $this->doRedirect(); } else { $proceed = false; // check if the step to be processed is valid if (strlen($action) == 0) $action = $this->getExpectedStep(); if ($this->stepCanBeProcessed($action)) { if ($this->getStepNumber($action) <= $this->getStepNumber($this->getExpectedStep())) $proceed = true; else $proceed = false; } if ($proceed) { if ($process) { // clear out errors $this->_errors = array(); // processing callback must exist and validate to proceed $callback = 'process_' . $action; $complete = method_exists($this, $callback) && $this->$callback($form); $this->container[$this->_step_status_key][$action] = $complete; if ($complete) $this->setCurrentStep($this->getFollowingStep($action)); // all ok, go to next step else $this->setCurrentStep($action); // error occurred, redo step // final processing once complete if ($this->isComplete()) $this->completeCallback(); $this->doRedirect(); } else $this->setCurrentStep($action); } else // when initally starting the wizard $this->setCurrentStep($this->getFirstIncompleteStep()); } // setup any required data for this step $callback = 'prepare_' . $this->getStepName(); if (method_exists($this, $callback)) $this->$callback(); } /** * completeCallback * * Function to run once the final step has been processed and is valid. * This should be overwritten in child classes */ function completeCallback() { } function doRedirect() { if ($this->coalesce($this->options['redirectAfterPost'], false)) { $redir = $_SERVER['REQUEST_URI']; $redir = preg_replace('/\?' . preg_quote($_SERVER['QUERY_STRING'], '/') . '$/', '', $redir); header('Location: ' . $redir); exit; } } /** * isComplete * * Check if the form is complete. This can only be properly determined * after process() has been called. * * @return bool True if the form is complete and valid, false if not */ function isComplete() { return $this->_complete; } /** * setCurrentStep * * Sets the current step in the form. This should generally only be * called internally but you may have reason to change the current * step. * * @param string $step The step to set as current */ function setCurrentStep($step) { if (is_null($step) || !$this->stepExists($step)) { $this->_complete = true; $this->container[$this->_step_expected_key] = null; } else { $this->_currentStep = $step; $this->container[$this->_step_expected_key] = $step; } } function getExpectedStep() { $step = $this->coalesce($this->container[$this->_step_expected_key], null); if ($this->stepExists($step)) return $step; return null; } /** * stepExists * * Check if the given step exists * * @param string $stepname The name of the step to check for * @return bool True if the step exists, false if not */ function stepExists($stepname) { return array_key_exists($stepname, $this->_steps); } /** * getStepName * * Get the name of the current step * * @return string The name of the current step */ function getStepName() { return $this->_currentStep; } /** * getStepNumber * * Gets the step number (from 1 to N where N is the number of steps * in the wizard) of the current step * * @param string $step Optional. The step to get the number for. If null then uses current step * @return int The number of the step. 0 if something went wrong */ function getStepNumber($step = null) { $steps = array_keys($this->_steps); $numSteps = count($steps); if (strlen($step) == 0) $step = $this->getStepName(); $ret = 0; for ($n = 1; $n <= $numSteps && $ret == 0; $n++) { if ($step == $steps[$n-1]) $ret = $n; } return $ret; } function stepCanBeProcessed($step) { $steps = array_keys($this->_steps); $numSteps = count($steps); for ($i = 0; $i < $numSteps; $i++) { $_step = $steps[$i]; if ($_step == $step) break; if (!$this->container[$this->_step_status_key][$_step]) return false; } return true; } /** * getStepProperty * * Retrieve a property for a given step. At this stage, the only * property steps have is a title property. * * @param string $key The key to get a property for * @param mixed $default The value to return if the key isn't found * @return mixed The property value or the default value */ function getStepProperty($key, $default = null) { $step = $this->getStepName(); if (isset($this->_steps[$step][$key])) return $this->_steps[$step][$key]; return $default; } /** * getFirstStep * * Get the step name of the first step * * @return string The name of the first step, or null if no steps */ function getFirstStep() { $steps = array_keys($this->_steps); return count($steps) > 0 ? $steps[0] : null; } function getFirstIncompleteStep() { $steps = array_keys($this->_steps); $numSteps = count($steps); for ($i = 0; $i < $numSteps; $i++) { $_step = $steps[$i]; if (!array_key_exists($this->_step_status_key, $this->container) || !$this->container[$this->_step_status_key][$_step]) return $_step; } return null; } /** * getPreviousStep * * Gets the step name of the previous step. If the current * step is the first step, then null is returned * * @return string The name of the previous step, or null */ function getPreviousStep($step) { $ret = null; $steps = array_keys($this->_steps); $done = false; foreach ($steps as $s) { if ($s == $step) { $done = true; break; } $ret = $s; } return $ret; } /** * getFollowingStep * * Get the step name of the next step. If the current * step is the last step, returns null * * @return string The name of the next step, or null */ function getFollowingStep($step) { $ret = null; $steps = array_keys($this->_steps); $ready = false; foreach ($steps as $s) { if ($s == $step) $ready = true; else if ($ready) { $ret = $s; break; } } return $ret; } /** * addStep * * Adds a step to the wizard * * @param string $stepname The name of the step * @param string $title The title of the current step */ function addStep($stepname, $title) { if (array_key_exists($stepname, $this->_steps)) { $this->addError('step', 'Step with name ' . $stepname . ' already exists'); return; } $this->_steps[$stepname] = array('title' => $title); if (!array_key_exists($this->_step_status_key, $this->container)) $this->container[$this->_step_status_key] = array(); if (!array_key_exists($stepname, $this->container[$this->_step_status_key])) $this->container[$this->_step_status_key][$stepname] = false; } /** * isFirstStep * * Check if the current step is the first step * * @return bool True if the current step is the first step */ function isFirstStep() { $steps = array_keys($this->_steps); return count($steps) > 0 && $steps[0] == $this->getStepName(); } /** * isLastStep * * Check if the current step is the last step * * @return bool True if the current step is the last step */ function isLastStep() { $steps = array_keys($this->_steps); return count($steps) > 0 && array_pop($steps) == $this->getStepName(); } /** * setValue * * Sets a value in the container * * @param string $key The key for the value to set * @param mixed $val The value */ function setValue($key, $val) { $this->container[$key] = $val; } /** * getValue * * Gets a value from the container * * @param string $key The key for the value to get * @param mixed $default The value to return if the key doesn't exist * @return mixed Either the key's value or the default value */ function getValue($key, $default = null) { return $this->coalesce($this->container[$key], $default); } /** * clearContainer * * Removes all data from the container. This is primarily used * to reset the wizard data completely */ function clearContainer() { foreach ($this->container as $k => $v) unset($this->container[$k]); } /** * coalesce * * Initializes a variable, by returning either the variable * or a default value * * @param mixed &$var The variable to fetch * @param mixed $default The value to return if variable doesn't exist or is null * @return mixed The variable value or the default value */ function coalesce(&$var, $default = null) { return isset($var) && !is_null($var) ? $var : $default; } /** * addError * * Add an error * * @param string $key An identifier for the error (e.g. the field name) * @param string $val An error message */ function addError($key, $val) { $this->_errors[$key] = $val; } /** * isError * * Check if an error has occurred * * @param string $key The field to check for error. If none specified checks for any error * @return bool True if an error has occurred, false if not */ function isError($key = null) { if (!is_null($key)) return array_key_exists($key, $this->_errors); return count($this->_errors) > 0; } function getError($key) { return array_key_exists($key, $this->_errors) ? $this->_errors[$key] : null; } } ?> Questions: 1. How would I implement the above code? In other words, where do I save the backend? Unfortunately my php experience is limited to CMS packages that do all the installation for me. 2. Once the user hits the final "submit" button, how can I have the data both save into a database and E-mail me a copy? Thanks and let me know if you need further explanation Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.