PHP

Basic State Controller for a Checkout

<?php

/**
 *
 * @ This class allows us to achieve stateful routing
 *   It only tracks current, completed and previous steps
 *   If the user presses the back button this will do nothing.
 *
 *   An example of it's usage is in the summary page:
 *    //-- You press edit on the address
 *    //-- This script will hold the information of the previous step (summary)
 *    //-- When you enter a new address or select another it understands that
 *         the summary page is where you need to go and will place you there.
 *
 */
class checkout_state_controller
{

    ///* Previous stage
    protected $previous;

    ///* Current stage
    protected $current;

    ///* All the stages in an array
    protected $stages = array();

    ///* Singleton instance of this class
    private static $instance;

    /**
     * Shopping basket stages
     *
     * @ This function returns an array of the stages of the shopping basket.
     *   It allows this class to make a descision about which page to redirect to.
     *   Make sure these are in order.
     *
     * @params void
     * @return Array $stages
     */
    public function stages()
    {
        return array(
            "shopping_basket" => new stage(HTTPS_BASE_URL . "/shopping_basket.php"),
            "welcome" => new stage(HTTPS_BASE_URL . "/checkout/welcome.php"),
            "shipping_address" => new stage(HTTPS_BASE_URL . "/checkout/shipping_address.php"),
            "payment" => new stage(HTTPS_BASE_URL . "/checkout/payment.php"),
            "billing_address" => new stage(HTTPS_BASE_URL . "/checkout/billing_address.php"),
            "summary" => new stage(HTTPS_BASE_URL . "/checkout/summary.php"),
            "confirmation" => new stage(HTTPS_BASE_URL . "/checkout/confirmation.php")
        );
    }

    /**
     * @ This is the singleton method as defined by the singleton pattern
     *   This ensures we only ever work on one of this object type.
     *
     * @param void
     * @return object $this
     */
    public static function getInstance()
    {
        if (!isset(self::$instance)) {
            $c = __CLASS__;
            self::$instance = new $c;
        }

        return self::$instance;
    }

    /**
     * Constructor
     *
     * @ In the constructor we get the stages and read from session
     *
     * @param void
     * @return void
     */
    private function __construct()
    {
        $this->stages = $this->stages();
        $this->read();
    }

    /**
     * PHP_SELF replacement echoer
     *
     * @ This function is designed to echo a string that will
     *   replace the insecure PHP_SELF function.
     *
     * @params void
     * @return string $url
     */
    public function echo_self()
    {
        return $this->stages[$this->current]->url;
    }

    /**
     * Echo stage url
     *
     * @ This allows us to quickly and easily echo a stage url
     *
     * @params string $stage
     * @return string $url
     */
    public function stage_url($stage)
    {
        return $stage ? $this->stages[$stage]->url : "";
    }

    /**
     * Sets current stage
     *
     * @ This simply sets the active stage
     *
     * @param string $caption
     *
     */
    public function set_active_stage($caption)
    {
        if ($this->current) {
            $this->previous = $this->current;
        }

        $this->current = $caption;
        $this->write();

    }

    /**
     * Routing Function
     *
     * @ This function will route the user through the cart.
     *   It allows for understanding completed sections of
     *   the checkout and redirecting between them as needed
     *
     * @param string $caption
     * @return boolean $success
     */
    public function route($caption = null)
    {

        /**
         * Support netsuite iframe
         *
         * @ To support the netsuite iframe we go on faith saying if the session variable is set
         *   then the page is completed.
         */
        if (isset($_SESSION['payment_selected_payment_option'])) {
            $this->stages["payment"]->completed = true;
        }

        /** Lets make current previous if current exists **/
        if ($this->current) {
            $this->previous = $this->current;
        }

        /** Lets set the current **/
        if (array_key_exists($caption, $this->stages) && $caption) {
            $this->current = $caption;
        } else {
            return false;
        }

        /** We have now completed this stage **/
        $this->stages[$this->current]->completed = true;

        /** reset and touch the array just to be sure we have the right position **/
        $this->setCurrent($this->current);

        /** Lets get the next stage **/
        $next = true;

        while ($next) {

            //* Get next stage
            $next_stage = next($this->stages);

            //* Is that stage completed?
            if (!$next_stage->completed) {

                //* If not set current to that stage and redirect the user
                $this->current = key($this->stages);
                header("Location: " . $next_stage->url);
                $next = false;
            }
        }

        /** Write to session **/
        $this->write();

        return true;
    }

    /**
     * A PHP_SELF override
     *
     * @ This function replaces PHP_SELF for the checkout
     *
     * @param void
     * @return header() $location
     */
    public function to_self()
    {
        header("Location: " . $this->stages[$this->current]->url);
    }

    /**
     * Goto Function
     *
     * @ This function will redirect to a specific stage
     *
     * @param string $stage
     * @return void
     */
    public function goto_stage($stage)
    {
        header("Location: " . $this->stages[$stage]->url);
    }

    /**
     * Go back function
     *
     * @ This function will redirect a user to the previous page
     *   Will not work unless you use set_active_stage at the top
     *   of the page in question
     *
     * @param string $stage
     * @return void
     */
    public function go_back($stage)
    {
        header("Location: " . $this->stages[$this->previous]->url);
    }

    /**
     * Array SetCurrent
     *
     * @ This function will allow you to set a current position of the array
     *
     * @param string $step
     * @return (Object)stage $current_stage
     */
    public function setCurrent($stage)
    {
        reset($this->stages);
        for ($i = 1; $i <= 8; $i++) {
            if ($this->stages[$i] == $stage) {
                break;
            }
        }
        return current($this->stages);
    }

    /**
     * Array getNext
     *
     * @ This function will allow you to get the next stage in the list
     *
     * @param void
     * @return (Object)stage $nextstage
     */
    public function getNext()
    {
        self::setCurrent($this->current);
        return next($this->stages);
    }

    /**
     * Array getPrev
     *
     * @ This function will allow you to get the previous stage in the list
     *
     * @param void
     * @return (Object)stage $previoustage
     */
    public function getPrev()
    {
        self::setCurrent($this->current);
        return prev($this->stages);
    }

    /**
     * Array getEnd
     *
     * @ This function will return the end entry of the stage array
     *
     * @param void
     * @return (Object)stage $endstage
     */
    public function getEnd()
    {
        return end($this->stages);
    }

    /**
     * Store read function
     *
     * @ This function will read the stored class from session.
     *   We do not store the object since you should never do that so
     *   instead we translate the object in a session storable array.
     *
     * @param void
     * @return void
     */
    public function read()
    {
        $checkout_state_controller = $_SESSION['checkout_state_controller'];

        if ($checkout_state_controller) {

            $this->previous = $checkout_state_controller['previous'];
            $this->current = $checkout_state_controller['current'];

            /** Compose stage array **/
            foreach ($checkout_state_controller['stages'] as $stage => $completed) {
                $this->stages[$stage]->completed = $completed;
            }
        }
    }

    /**
     * Store write function
     *
     * @ This function will write this class to session
     *
     * @param void
     * @return void
     */
    public function write()
    {
        $_SESSION['checkout_state_controller']['previous'] = $this->previous;
        $_SESSION['checkout_state_controller']['current'] = $this->current;

        /** Compose stage array **/
        $stages_arr = Array();
        foreach ($this->stages as $stage_name => $object) {
            $stages_arr[$stage_name] = $object->completed;
        }

        $_SESSION['checkout_state_controller']['stages'] = $stages_arr;

    }

    /**
     * Store delete function
     *
     * @ This function will delete a store version of this class from session
     *
     * @param void
     * @return void
     */
    public function delete()
    {
        unset($_SESSSION['checkout_state_controller']);
    }

    /**
     * @ This is the __clone magic called when a user tries to clone this class
     *   This function will return an error since singleton cannot be copied
     */
    public function __clone()
    {
        trigger_error('You cannot clone this class.', E_USER_ERROR);
    }

}

/**
 * This is the stage entity
 *
 * @ This is the object in which a stage is place.
 *   It allows us to control excatly what a stage means
 *
 */
class stage
{

    protected $url;
    protected $required;
    protected $completed = false;

    public function __construct($url, $required = false)
    {
        $this->url = $url;
        $this->required = $required;
    }

    public function __get($k)
    {
        return $this->{$k};
    }

    public function __set($k, $v)
    {
        $this->{$k} = $v;
    }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s