Jump to content

Multi-step form w/E-mail


dslax27

Recommended Posts

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:

 

 

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

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.