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 Link to comment https://forums.phpfreaks.com/topic/96647-multi-step-form-we-mail/ Share on other sites More sharing options...
Recommended Posts
Archived
This topic is now archived and is closed to further replies.