- <?php
- /* vim: set number autoindent tabstop=2 shiftwidth=2 softtabstop=2: */
-
- /**
- * A class that implements a Wizard with HTML_QuickForm.
- *
- * PHP versions 4 and 5
- *
- * LICENSE: This source file is subject to version 3.0 of the PHP license
- * that is available through the world-wide-web at the following URI:
- * http://www.php.net/license/3_0.txt. If you did not receive a copy of
- * the PHP License and are unable to obtain it through the web, please
- * send a note to license@php.net so we can mail you a copy immediately.
- *
- * @category HTML
- * @package HTML_QuickForm_Wizard
- * @author Fabio Ambrosanio <fabio@ambrosanio.com>
- * @license http://www.php.net/license/3_01.txt PHP
- * @version @package_version@
- *
- * $Id: Wizard.php,v 1.8 2007/04/19 06:09:06 fabamb Exp $
- */
-
- require_once 'HTML/QuickForm/Controller.php';
- require_once 'HTML/QuickForm/Action/Display.php';
- require_once 'HTML/QuickForm/Action/Next.php';
- require_once 'HTML/QuickForm/Action/Back.php';
- require_once 'HTML/QuickForm/Action/Display.php';
-
- // Load some default action handlers
- require_once(dirname(__FILE__).'/actions/back.php');
- require_once(dirname(__FILE__).'/actions/next.php');
- require_once(dirname(__FILE__).'/actions/skip.php');
- require_once(dirname(__FILE__).'/actions/jump.php');
- require_once(dirname(__FILE__).'/actions/clear.php');
-
- define("WIZARD_DEFAULT_INPUT_VALUE", "_default_");
-
- /**
- * A class that implements a Wizard with HTML_QuickForm.
- *
- * This class implements a wizard whoese pages and flows are described by a FSM (Finite State Machine).
- * Next page is determined from the actual page, the "internal" state of the wizard and a set of rules.
- *
- * @author Fabio Ambrosanio <fabio@ambrosanio.com>
- * @category HTML
- * @package HTML_QuickForm_Wizard
- * @version @package_version@
- * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
- */
- class HTML_QuickForm_Wizard extends HTML_QuickForm_Controller
- {
- /**
- * List of states (pages in form of pageName => className)
- * of the wizard (FSM's set of states)
- *
- * @var array
- */
- var $states = array();
-
- /**
- * The first state (page) of the wizard
- * (FSM's starting state)
- *
- * @var string
- */
- var $startingState;
-
- /**
- * List of final states (pages) in which the wizard stops
- * (FSM's final states)
- *
- * @var array
- */
- var $finalStates = array();
-
- /**
- * Set of paths from a state to another
- * (FSM's transitions set)
- *
- * @var array
- */
- var $delta = array();
-
- /**
- * Function that determines the current input of the FSM,
- * as an array of values, used to establish the next page of the wizard
- *
- * @var function
- */
- var $inputProducer = null;
-
-
- /**
- * Constructor
- *
- * @param string $name name of the wizard
- * @param boolean $modal modal mode flag
- * @return Wizard
- */
- function HTML_QuickForm_Wizard($name, $modal = true)
- {
- // call parent constructor
- parent::HTML_QuickForm_Controller($name, $modal);
-
- // store the stack of pages into session
- if (!isset($_SESSION["_" . $_SERVER['PHP_SELF']. '_stack'])) {
- $_SESSION["_" . $_SERVER['PHP_SELF']. '_stack'] = array();
- }
-
- // sets default actions
- $this->addAction('display', new HTML_QuickForm_Action_Display());
- $this->addAction('back', new HTML_QuickForm_Wizard_back());
- $this->addAction('next', new HTML_QuickForm_Wizard_next());
- $this->addAction('jump', new HTML_QuickForm_Wizard_jump());
- $this->addAction('clear', new HTML_QuickForm_Wizard_clear());
- $this->addAction('skip', new HTML_QuickForm_Wizard_skip());
- }
-
- // {{{ addPage
-
-
-
- /**
- * Add a page to the wizard
- *
- * @param string $pageName name of the page
- * @param string $className class of the page
- * @param boolean $isStart if true the page represents the start state of the FSM
- * @param boolean $isFinal if true the page represents a final state of the FSM
- */
- function addPage($pageName, $className, $isStart, $isFinal = false)
- {
- $this->states[$pageName] = $className;
- if (count($this->states) == 1) {
- $this->startingState = $pageName;
- }
- if ($isStart) {
- $this->startingState = $pageName;
- }
- if ($isFinal && !in_array($pageName, $this->finalStates)) {
- $this->finalStates[] = $pageName;
- }
-
- // adds page to the controller
- $page = new $className($pageName);
- parent::addPage($page);
-
- // sets the custom 'back' action
- $page->addAction('back', new HTML_QuickForm_Wizard_back());
-
- // sets the custom 'next' action
- $page->addAction('next', new HTML_QuickForm_Wizard_next());
- }
-
- /**
- * Add a transition from a state (page) to another
- *
- * @param string $fromPage
- * @param mixed $input
- * @param string $toPage
- */
- function addTransition($fromPage, $input, $toPage)
- {
- if (is_null($input) || (string)$input == "") {
- $input = WIZARD_DEFAULT_INPUT_VALUE;
- }
- $this->delta[$fromPage][$input] = $toPage;
- }
-
-
- /**
- * Sets the producer of input to the FSM
- *
- * @param function $inputProducer new function that produces current input of the FSM
- */
- function setInputProducer($inputProducer)
- {
- $this->inputProducer =& $inputProducer;
- }
-
- /**
- * Checks if the specified page is final
- *
- * @param string $pageName
- * @return boolean true if $pageName is a final page, false otherwise
- */
- function isFinal($pageName)
- {
- return in_array($pageName, $this->finalStates);
- }
-
- /**
- * Returns an array representing the FSM
- *
- * @return array
- */
- function getFSM()
- {
- // builds fsm
- $fsm = array();
- foreach($this->states as $name => $className) {
- $state = array(
- 'className' => $className
- );
- if ($this->startingState == $name) {
- $state['isStart'] = "true";
- }
- if (in_array($name, $this->finalStates)) {
- $state['isFinal'] = "true";
- }
-
- $rules = array();
- $_delta = $this->delta[$name];
- if (is_array($_delta)) foreach ($_delta as $input => $next) {
- $rules[] = array(
- 'input' => $input,
- 'next' => $next,
- );
- }
-
- if (!empty($rules)) {
- $state[] = $rules;
- }
-
- $fsm[$name] = $state;
- }
- return $fsm;
- }
-
-
- /**
- * Set the FSM of the wizard
- *
- * @param array $fsm array representing a FSM
- */
- function setFSM($fsm)
- {
- // reset status
- $this->states = $this->finalStates = $this->delta = array();
- $this->startingState = null;
-
- // loop on pages
- foreach($fsm as $page) {
- $isStart = $isFinal = false;
- if (!isset($page['name']) || ($pageName = $page['name']) == "" ) {
- PEAR::raiseError("HTML_QuickForm_Wizard: Cannot find page name", 0, PEAR_ERROR_TRIGGER);
- }
-
- if (!isset($page['class']) || ($className = $page['class']) == "" ) {
- PEAR::raiseError("HTML_QuickForm_Wizard: Cannot find page class", 0, PEAR_ERROR_TRIGGER);
- }
-
- if (isset($page['start'])) {
- $isStart = $page['start'];
- }
- if (isset($page['final'])) {
- $isFinal = $page['final'];
- }
- if (isset($page['file'])) {
- include_once($page['file']);
- }
-
- // add the page to the wizard
- $this->addPage($pageName, $className, $isStart, $isFinal);
-
- // add transactions
- if (is_array($rules = $page['rules'])) {
- foreach ($rules as $rule) {
- $this->addTransition($pageName, $rule['input'], $rule['next']);
- }
- }
- }
- }
-
- /**
- * Import the FSM from a XML document
- *
- * @param string $xml filename of a XML document or XML itself
- * @param boolean $isFile if true $xml represents a filename
- */
- function fromXML($xml, $isFile = false)
- {
- if ($isFile) {
- $xml = file_get_contents($xml);
- }
-
- include_once('XML/Unserializer.php');
- $options = array(
- 'parseAttributes' => true,
- 'tagMap' => array('page' => "", 'rule' => "")
- );
- $xu = new XML_Unserializer($options);
- $status = $xu->unserialize($xml);
- if (PEAR::isError($status)) {
- PEAR::raiseError($status->getMessage(), $status->getCode(), PEAR_ERROR_TRIGGER);
- }
-
- $fsm = $xu->getUnserializedData();
- $this->setFSM($fsm);
- }
-
-
- /**
- * Resets the wizard
- *
- */
- function reset()
- {
- $this->container(true);
- $_SESSION["_" . $_SERVER['PHP_SELF']. '_stack'] = array();
- }
-
- /**
- * Returns the name of the current page
- *
- * @return string
- */
- function getCurrentPage()
- {
- $stack = $_SESSION["_" . $_SERVER['PHP_SELF']. '_stack'];
- if (!is_array($stack) or empty($stack)) {
- return $this->startingState;
- }
- reset($stack);
- return end($stack);
- }
-
- /**
- * Returns the name of the previous page
- *
- * @param string $pageName
- * @return string
- */
- function getBackName($pageName)
- {
- // gets back page from stack
- $backName = $this->_pop($pageName);
- return $backName;
- }
-
- /**
- * Returns the name of the next page
- * (overloading of Controller method)
- *
- * @return string
- */
- function getNextName($pageName)
- {
- $nextName = $this->_getNextPage($pageName);
- if (!$nextName) {
- $nextName = parent::getNextName($pageName);
- }
-
- if ($nextName) {
- $this->_push($pageName, $nextName);
- }
- return $nextName;
- }
-
- /**
- * Returns the names of all pages
- *
- * @return array
- */
- function getPageNames()
- {
- return array_keys($this->_pages);
- }
-
- /**
- * Load values into pages' elements
- *
- * @param array $values values to put into pages' elements
- */
- function loadValues($values)
- {
- $data =& $this->container();
- $_pages = $this->getPageNames();
- foreach($values as $key => $v) {
- if (in_array($key, $_pages)) {
- $data['values'][$key] = $v;
- } else {
- $data[$key] = $v;
- }
- }
- }
-
-
- /**
- * Returns next page that match specified page and current input of the FSM
- *
- * @return string
- */
- function _getNextPage($pageName)
- {
- $input = $this->_getInput();
-
- $nextPage = null;
-
- // search $delta for next pages
- $rules = $this->delta[$pageName];
- if (!is_array($input)) {
- if (isset($rules[$input])) {
- $nextPage = $rules[$input];
- }
- } else foreach($input as $i) {
- if (isset($rules[$i])) {
- $nextPage = $rules[$i];
- break;
- }
- }
-
- // if no page found, try with default input
- if (is_null($nextPage)) {
- if (isset($rules[WIZARD_DEFAULT_INPUT_VALUE])) {
- $nextPage = $rules[WIZARD_DEFAULT_INPUT_VALUE];
- }
- }
-
- return $nextPage;
- }
-
-
- /**
- * Returns the current input of the wizard
- *
- * @return array
- */
- function _getInput()
- {
- $input = array();
- if (is_callable($this->inputProducer)) {
- $input = call_user_func($this->inputProducer, $this->container());
- }
- return $input;
- }
-
-
- /**
- * Pushes the new page into the stack of pages
- *
- * @param string $old actual page
- * @param string $new new page
- */
- function _push($old, $new)
- {
- $stack = $_SESSION["_" . $_SERVER['PHP_SELF']. '_stack'];
- if (!is_array($stack)) $stack = array();
-
- reset($stack);
-
- # se la vecchia pagina non è nello stack (perchè?) la inserisco
- if (!in_array($old, $stack)) {
- array_push($stack, $old);
- }
- # se la nuova pagina è già nello stack (perchè?) correggo lo stack
- if (in_array($new, $stack)) {
- $pos = array_search($new, $stack);
- array_splice($stack, $pos);
- }
- # inserisco la nuova pagina nello stack
- array_push($stack, $new);
- $_SESSION["_" . $_SERVER['PHP_SELF']. '_stack'] = $stack;
- }
-
- /**
- * Pops the page previous the specified page from the stack
- *
- * @param string $current current page
- * @return string page in stack before specified page
- */
- function _pop($current)
- {
- $stack = $_SESSION["_" . $_SERVER['PHP_SELF']. '_stack'];
- if (!is_array($stack)) $stack = array();
-
- reset($stack);
-
- # se la pagina attuale è contenuta nello stack (come ci aspettiamo)
- # elimino gli elementi dello stack sopra la pagina
- if (in_array($current, $stack)) {
- $pos = array_search($current, $stack);
- array_splice($stack, $pos);
- }
- $back = end($stack);
- $_SESSION["_" . $_SERVER['PHP_SELF']. '_stack'] = $stack;
-
- if ("$back" == '') $back = $current;
-
- return $back;
- }
- }
- ?>