<?php
/**
* A 'runner' - environment for managing form wizards
*
* @author Yuri Makassiouk <racer@bfpg.ru>
*/
//include('constants.php3');
//this is the directory where forms (wizard's screens) reside:
$wizardRoot = '';
if (!isset($wizardPage))
//initialize sequence. (This value should normally not be hard-coded)
$wizardPage = 'reg_form01.php';
clearstatcache();
while ($wizardPage) {
$page = ($wizardRoot . $wizardPage);
//echo ' ' . $wizardPage . ' '; // - uncomment this to monitor what's going on
$wizardPage = ''; // this will stall the wizard
if (file_exists($page)) {
// the included file may either set the global variable $wizardPage
// and that will continue the loop instantly
// or it may have the pair 'wizardPage=...' get submitted
// with a form the file contains.
include($page);
}
else {
echo "<br><b>Form wizard:</b> cannot find '$page', aborting wizard";
// this break isn't really necessary - no included file - nobody to
// set $wizardPage being != ''. But just in case, you know =)
break;
}
}
?>
<?php
/**
* An abstract class implementing generic functionality for processing user's input
*
* This class encapsulates generic functions for working
* with data coming from user forms. Descendants must only override certain
* functions that perform context-specific tasks, like custom checking of
* data, storing correct data, etc.
*
* @author Yuri Makassiouk <racer@bfpg.ru>,
* Mission & Media <firma@missionandmedia.com>
*/
class FormProcessor {
var $Name;
var $FirstTime;
var $Values;
var $Errors = array();
var $ErrorMessageFormat = '<br>%s';
var $wizardPage;
var $fieldPrefix = 'mm_form_data_';
function FormProcessor($Name, $wPage='') {
$this->Name = $Name;
$this->wizardPage = $wPage;
$this->Values = $GLOBALS[$this->fieldPrefix.$this->Name];
$this->FirstTime = (count($this->Values) == 0);
if (!$this->FirstTime)
$this->CustomCheck();
if ($this->IsCompleted()) {
$this->StoreData();
$GLOBALS['wizardPage'] = $this->NextWizardPage();
}
else
$this->DisplayForm();
}
function IsCompleted() {
return (!$this->FirstTime && count($this->Errors)<=0);
}
function CustomCheck() {}
//abstract
function DisplayForm() {}
//abstract
function NextWizardPage() {}
//abstract
function StoreData() {}
//abstract
function Additional() {
if ($this->wizardPage) :
?>
<input type="Hidden" name="wizardPage" value="<?php echo $this->wizardPage?>">
<?php endif;
}
function Set($Name, $Value) {
$this->$Name = $Value;
}
function ErrorReport($Name) {
if (isset($this->Errors[$Name]))
printf($this->ErrorMessageFormat, $this->Errors[$Name]);
}
function GetInitialValue($Name) {
if (isset($this->Values[$Name]))
return $this->Values[$Name];
else
return false;
}
function InitialValue($Name) {
echo $this->GetInitialValue($Name);
}
}
?>
$this->Name is form's name that we pass as constructor's parameter.
$this->fieldPrefix is defined as data member of the class, but you can
think of it as of a constant value that remains the same throughout lifetime
of instances of the class. Its only purpose is to transparently add 'uniqueness'
to the field names, so that they do not accidentally get mixed with some other
program's objects.The first thing generic constructor does is it 'smells the air around'. It assigns
global variable (whether it is set or not) with this magic name to $this->Values.
Then, if $this->Values is not empty, i.e. form's fields were submitted before,
FormProcessor decides that it is the not the first time this form is on the
arena. In this case, it calls the abstract $this->CustomCheck() member function.
Overriding this function in sub-classes allows these classes' authors to implement
whatever custom conditions checking they fancy. Of course, being a member function
of the class gives CustomCheck() access to $this->Values array, which contains
all values that form is working with as an associative array.
If overridden CustomCheck() finds error(s) that should not allow the form to
be considered as completed, it should write error messages in $this->Errors
data member. It is also treated as an associative array with the same association
as $this->Values. In other words, each submitted value has a potential error message
linked with it which, if not empty, indicates a fault in this value.
<?php
/**
* A small hierarchy of classes that
* serve as object wrappers for HTML form's inputs
*/
/**
* An abstract class representing generic form control
*
* @author Yuri Makassiouk <racer@bfpg.ru>,
* Mission & Media <firma@missionandmedia.com>
*/
class FormControl {
var $Name;
var $form;
var $Value;
var $Attributes;
var $FormName;
var $InputName;
function FormControl($Aform, $AName, $AValue='', $AAttributes='') {
$this->Name = $AName;
$this->form = $Aform;
$this->Value =
($this->form->FirstTime)?$AValue:($this->form->Values[$this->Name]);
$this->Attributes = $AAttributes;
$this->FormName = $Aform->Name;
$this->InputName = sprintf("%s%s[%s]",
$this->form->fieldPrefix, $this->FormName, $this->Name);
$this->Display();
}
function InputName() {
echo $this->InputName;
}
function Display() {
echo $this->Render();
}
function Render() {}
//abstract
}
/**
* Class representing a text control
*
*/
class TextInput extends FormControl {
function Render() {
return "<input type=\"Text\" name=\"".
$this->InputName."\" value=\"$this->Value\" $this->Attributes>";
}
}
/**
* Class representing a set of radio buttons
*
*/
class RadioButtons extends FormControl {
var $options;
var $ItemFormat = '%RBTN %LABEL';
var $separator = '<br>';
function RadioButtons($Aform, $AName,
$OptionList, $AValue='', $AAttributes='',
$AItemFormat='%RBTN %LABEL', $Aseparator = '<br>') {
$this->options = $OptionList;
$this->ItemFormat = $AItemFormat;
$this->separator = $Aseparator;
$this->FormControl($Aform, $AName, $AValue, $AAttributes);
}
function Render() {
$i=0;
$out = '';
while (list($key, $val)=each($this->options)) {
$item = $this->ItemFormat;
$item = str_replace('%RBTN',
sprintf("<input type=\"Radio\" name=\"%s\"
value=\"${key}\" $this->Attributes%s>",
$this->InputName, $key==$this->Value?' checked':''), $item);
$item = str_replace('%LABEL', $val, $item);
if (++$i!=count($this->options))
$item .= $this->separator;
$out .= $item;
}
return $out;
}
}
?>
<?php
define('MinNameLength', 5);
class RegForm01 extends FormProcessor {
function CustomCheck() {
if (strlen($this->Values['Name']) < MinNameLength)
$this->Errors['Name'] =
'Username should contain at least ' . MinNameLength . ' symbols';
if ($this->Values['Email']) {
if (!eregi('^[-!#$%&\'*+\\./0-9=?A-Z^_\`a-z{|}~]+
@[-!#$%&\'*+\\/0-9=?A-Z^_\`a-z{|}~]+\.[-!#$%&'*+
'\\./0-9=?A-Z^_\`a-z{|}~]+$', $this->Values['Email']))
$this->Errors['Email'] = 'Value entered does not
appear to be a valid e-mail address';
} else {
$this->Errors['Email'] =
'E-mail address is required to complete subscription';
}
}
function NextWizardPage() {
//decision about what wizard's screen to load next is made here,
//based on the submitted values:
return ($this->Values['PaymentMethod'] ==
'P')?'reg_form_free.php':'reg_form_payed.php';
}
function StoreData() {
// we will not discuss storing persistent variables in this article.
// here would normally appear code that updates a database or
// stores session variables
STORE_VARIABLE($this->Values['Name']);
}
function DisplayForm() {
// I'm not sure you meet a form like this on a real web-site =), but I hope
// it'll do for the example
?>
<form action="<?php echo $PHP_SELF?>" method="POST">
Username: <?php new TextInput($this, 'Name', '', 'size="30" maxlength="100"')?>
<?php $this->ErrorReport('Name')?>
Email: <?php new TextInput($this, 'Email', '', 'size="30" maxlength="100"')?>
<?php $this->ErrorReport('Email')?>
<br><br>
Subscription type:<br>
<?php new RadioButtons($this, 'SubscrType', array('P'=>'Preview', 'M'=>'Membership'), 'P')?>
<?php $this->ErrorReport('SubscrType')?>
<br>
<br>
<?php $this->Additional()?>
<input type="submit" name="Submit" value="Submit">
</form>
<?php
}
}
$form = new RegForm01('RegForm01', 'reg_form01.php');
?>
This code can also be downloaded from here.