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
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • 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.