mirror of
https://github.com/silverstripe/silverstripe-multiform
synced 2024-10-22 09:05:49 +00:00
Tagged multiform 0.2
This commit is contained in:
commit
e1eced2486
18
CHANGELOG
Normal file
18
CHANGELOG
Normal file
@ -0,0 +1,18 @@
|
||||
0.2:
|
||||
- ENHANCEMENT Updated entities and added german translation for multiform
|
||||
- ENHANCEMENT Making multiform module translatable
|
||||
- ENHANCEMENT Added MultiFormStep->saveInto() to simulate Form->saveInto()
|
||||
- ENHANCEMENT Made MultiForm->prev() do the same behaviour for saving data
|
||||
- ENHANCEMENT Added MultiForm->getSavedSteps()
|
||||
- BUGFIX Removing url_type which isnt very useful
|
||||
- BUGFIX $this->form wasn't accessible on MultiFormStep
|
||||
- ENHANCEMENT Correct use of parent::construct() so that fields, actions
|
||||
- BUGFIX SQL injection possibility fix on MultiForm->getSessionRecordByID()
|
||||
- BUGFIX Disable security token inherited from Form, which isn't required
|
||||
- BUGFIX Made MultiFormPurgeTask greatly simplified, and workable
|
||||
- ENHANCEMENT Allowed static $ignored_fields to be overloaded on subclass of MultiForm
|
||||
- BUGFIX Use $nextStep->Link and $prevStep->Link() for prev() and next() on MultiForm
|
||||
- API CHANGE Ticket #2562 - Cleaner instanciation of MultiForm subclass without having to call ->init()
|
||||
|
||||
0.1:
|
||||
- initial release
|
24
LICENSE
Normal file
24
LICENSE
Normal file
@ -0,0 +1,24 @@
|
||||
* Copyright (c) 2008, Silverstripe Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the <organization> nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY Silverstripe Ltd. ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL Silverstripe Ltd. BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
36
README
Normal file
36
README
Normal file
@ -0,0 +1,36 @@
|
||||
###############################################
|
||||
SilverStripe MultiForm module
|
||||
###############################################
|
||||
|
||||
April 3rd, 2008
|
||||
|
||||
This module acts as a "wizard" for multiple form steps with automatic
|
||||
session-saving of data, and versatile start/finish customizations.
|
||||
|
||||
Maintainer Contact
|
||||
-----------------------------------------------
|
||||
Sean Harvey (Nickname: sharvey, halkyon)
|
||||
<sean (at) silverstripe (dot) com>
|
||||
|
||||
Requirements
|
||||
-----------------------------------------------
|
||||
SilverStripe 2.2.2 or higher is required.
|
||||
|
||||
Documentation
|
||||
-----------------------------------------------
|
||||
http://doc.silverstripe.com/doku.php?id=modules:multiform
|
||||
|
||||
Installation Instructions
|
||||
-----------------------------------------------
|
||||
1) Copy the "multiform" directory into your main SilverStripe directory
|
||||
2) run db/build?flush=1
|
||||
3) View documentation (link above) on how to model your forms into steps
|
||||
|
||||
Usage Overview
|
||||
-----------------------------------------------
|
||||
If you ever add a new step, you must run db/build?flush=1, as the step requires
|
||||
a class name in the database to store it's session state, and data.
|
||||
|
||||
Known issues:
|
||||
-----------------------------------------------
|
||||
Please check http://open.silverstripe.com for known issues.
|
5
_config.php
Normal file
5
_config.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
// This file is required in order for SilverStripe to detect this directory as a module.
|
||||
|
||||
?>
|
556
code/MultiForm.php
Normal file
556
code/MultiForm.php
Normal file
@ -0,0 +1,556 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* MultiForm manages the loading of single form steps, and acts as a state
|
||||
* machine that connects to a {@link MultiFormSession} object as a persistence
|
||||
* layer.
|
||||
*
|
||||
* CAUTION: If you're using controller permission control,
|
||||
* you have to allow the following methods:
|
||||
* <code>
|
||||
* static $allowed_actions = array('next','prev');
|
||||
* </code>
|
||||
*
|
||||
* @package multiform
|
||||
*/
|
||||
abstract class MultiForm extends Form {
|
||||
|
||||
/**
|
||||
* A session object stored in the database, to identify and store
|
||||
* data for this MultiForm instance.
|
||||
*
|
||||
* @var MultiFormSession
|
||||
*/
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* Defines which subclass of {@link MultiFormStep} should be the first
|
||||
* step in the multi-step process.
|
||||
*
|
||||
* @var string Classname of a {@link MultiFormStep} subclass
|
||||
*/
|
||||
protected static $start_step;
|
||||
|
||||
/**
|
||||
* Set the casting for these fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $casting = array(
|
||||
'CompletedStepCount' => 'Int',
|
||||
'TotalStepCount' => 'Int',
|
||||
'CompletedPercent' => 'Float'
|
||||
);
|
||||
|
||||
/**
|
||||
* These fields are ignored when saving the raw form data into session.
|
||||
* This ensures only field data is saved, and nothing else that's useless
|
||||
* or potentially dangerous.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $ignored_fields = array(
|
||||
'url',
|
||||
'executeForm',
|
||||
'MultiFormSessionID',
|
||||
'SecurityID'
|
||||
);
|
||||
|
||||
/**
|
||||
* Start the MultiForm instance.
|
||||
*
|
||||
* @param Controller instance $controller Controller this form is created on
|
||||
* @param string $name The form name, typically the same as the method name
|
||||
*/
|
||||
public function __construct($controller, $name) {
|
||||
|
||||
// Set up the session for this MultiForm instance
|
||||
$this->setSession();
|
||||
|
||||
// Get the current step available (Note: either returns an existing
|
||||
// step or creates a new one if none available)
|
||||
$currentStep = $this->getCurrentStep();
|
||||
|
||||
// Set the step returned above as the current step
|
||||
$this->setCurrentStep($currentStep);
|
||||
|
||||
// Set the form of the step to this form instance
|
||||
$currentStep->form = $this;
|
||||
|
||||
// Set up the fields for the current step
|
||||
$fields = $currentStep->getFields();
|
||||
|
||||
// Set up the actions for the current step
|
||||
$actions = $this->actionsFor($currentStep);
|
||||
|
||||
// Set up validation (if necessary)
|
||||
// @todo find a better way instead of hardcoding a check for action_prev in order to prevent validation when hitting the back button
|
||||
$validator = null;
|
||||
if(empty($_REQUEST['action_prev'])) {
|
||||
if($this->getCurrentStep()->getValidator()) {
|
||||
$validator = $this->getCurrentStep()->getValidator();
|
||||
}
|
||||
}
|
||||
|
||||
// Give the fields, actions, and validation for the current step back to the parent Form class
|
||||
parent::__construct($controller, $name, $fields, $actions, $validator);
|
||||
|
||||
// Set a hidden field in our form with an encrypted hash to identify this session.
|
||||
$this->fields->push(new HiddenField('MultiFormSessionID', false, $this->session->Hash));
|
||||
|
||||
// If there is saved data for the current step, we load it into the form it here
|
||||
//(CAUTION: loadData() MUST unserialize first!)
|
||||
if($currentStep->loadData()) {
|
||||
$this->loadDataFrom($currentStep->loadData());
|
||||
}
|
||||
|
||||
// Disable security token - we tie a form to a session ID instead
|
||||
$this->disableSecurityToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor method to $this->controller.
|
||||
*
|
||||
* @return Controller this MultiForm was instanciated on.
|
||||
*/
|
||||
public function getController() {
|
||||
return $this->controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current step.
|
||||
*
|
||||
* If StepID has been set in the URL, we attempt to get that record
|
||||
* by the ID. Otherwise, we check if there's a current step ID in
|
||||
* our session record. Failing those cases, we assume that the form has
|
||||
* just been started, and so we create the first step and return it.
|
||||
*
|
||||
* @return MultiFormStep subclass
|
||||
*/
|
||||
public function getCurrentStep() {
|
||||
$startStepClass = $this->stat('start_step');
|
||||
|
||||
// Check if there was a start step defined on the subclass of MultiForm
|
||||
if(!isset($startStepClass)) user_error('MultiForm::init(): Please define a $startStep on ' . $this->class, E_USER_ERROR);
|
||||
|
||||
// Determine whether we use the current step, or create one if it doesn't exist
|
||||
if(isset($_GET['StepID'])) {
|
||||
$stepID = (int)$_GET['StepID'];
|
||||
$currentStep = DataObject::get_one('MultiFormStep', "SessionID = {$this->session->ID} AND ID = {$stepID}");
|
||||
} elseif($this->session->CurrentStepID) {
|
||||
$currentStep = $this->session->CurrentStep();
|
||||
} else {
|
||||
$currentStep = new $startStepClass();
|
||||
$currentStep->SessionID = $this->session->ID;
|
||||
$currentStep->write();
|
||||
}
|
||||
|
||||
return $currentStep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the step passed in as the current step.
|
||||
*
|
||||
* @param MultiFormStep $step A subclass of MultiFormStep
|
||||
* @return boolean The return value of write()
|
||||
*/
|
||||
protected function setCurrentStep($step) {
|
||||
$this->session->CurrentStepID = $step->ID;
|
||||
return $this->session->write();
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor method to $this->session.
|
||||
*
|
||||
* @return MultiFormSession
|
||||
*/
|
||||
function getSession() {
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the session.
|
||||
*
|
||||
* If MultiFormSessionID isn't set, we assume that this is a new
|
||||
* multiform that requires a new session record to be created.
|
||||
*
|
||||
* @TODO Fix the fact you can continually refresh and create new records
|
||||
* if MultiFormSessionID isn't set.
|
||||
*
|
||||
* @TODO Not sure if we should bake the session stuff directly into MultiForm.
|
||||
* Perhaps it would be best dealt with on a separate class?
|
||||
*/
|
||||
protected function setSession() {
|
||||
// If there's a MultiFormSessionID variable set, find that, otherwise create a new session
|
||||
if(isset($_GET['MultiFormSessionID'])) {
|
||||
$this->session = $this->getSessionRecord($_GET['MultiFormSessionID']);
|
||||
}
|
||||
|
||||
// If there was no session found, create a new one instead
|
||||
if(!$this->session) {
|
||||
$this->session = new MultiFormSession();
|
||||
$this->session->write();
|
||||
}
|
||||
|
||||
// Create encrypted identification to the session instance if it doesn't exist
|
||||
if(!$this->session->Hash) {
|
||||
$this->session->Hash = sha1($this->session->ID . '-' . microtime());
|
||||
$this->session->write();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of MultiFormSession.
|
||||
*
|
||||
* @param string $hash The unique, encrypted hash to identify the session
|
||||
* @return MultiFormSession
|
||||
*/
|
||||
function getSessionRecord($hash) {
|
||||
$SQL_hash = Convert::raw2sql($hash);
|
||||
return DataObject::get_one('MultiFormSession', "Hash = '$SQL_hash' AND IsComplete = 0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all steps saved in the database for the currently active session,
|
||||
* in the order they were saved, oldest to newest (automatically ordered by ID).
|
||||
* If you want a full chain of steps regardless if they've already been saved
|
||||
* to the database, use {@link getAllStepsLinear()}.
|
||||
*
|
||||
* @return DataObjectSet|boolean A set of MultiFormStep subclasses
|
||||
*/
|
||||
function getSavedSteps() {
|
||||
return DataObject::get(
|
||||
'MultiFormStep',
|
||||
sprintf("SessionID = '%s'",
|
||||
$this->session->ID
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a step which was previously saved to the database in the current session.
|
||||
* Caution: This might cause unexpected behaviour if you have multiple steps
|
||||
* in your chain with the same classname.
|
||||
*
|
||||
* @param string $className Classname of a {@link MultiFormStep} subclass
|
||||
* @return MultiFormStep
|
||||
*/
|
||||
function getSavedStepByClass($className) {
|
||||
return DataObject::get_one(
|
||||
'MultiFormStep',
|
||||
sprintf("SessionID = '%s' AND ClassName = '%s'",
|
||||
$this->session->ID,
|
||||
Convert::raw2sql($className)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a FieldSet of the FormAction fields for the given step.
|
||||
*
|
||||
* If the current step is the final step, we push in a submit button, which
|
||||
* calls the action {@link finish()} to finalise the submission. Otherwise,
|
||||
* we push in a next button which calls the action {@link next()} to determine
|
||||
* where to go next in our step process, and save any form data collected.
|
||||
*
|
||||
* If there's a previous step (a step that has the current step as it's next
|
||||
* step class), then we allow a previous button, which calls the previous action
|
||||
* to determine which step to go back to.
|
||||
*
|
||||
* If there are any extra actions defined in MultiFormStep->getExtraActions()
|
||||
* then that set of actions is appended to the end of the actions FieldSet we
|
||||
* have created in this method.
|
||||
*
|
||||
* @param $currentStep Subclass of MultiFormStep
|
||||
* @return FieldSet of FormAction objects
|
||||
*/
|
||||
function actionsFor($step) {
|
||||
// Create default multi step actions (next, prev), and merge with extra actions, if any
|
||||
$actions = new FieldSet();
|
||||
|
||||
// If the form is at final step, create a submit button to perform final actions
|
||||
// The last step doesn't have a next button, so add that action to any step that isn't the final one
|
||||
if($step->isFinalStep()) {
|
||||
$actions->push(new FormAction('finish', _t('MultiForm.SUBMIT', 'Submit')));
|
||||
} else {
|
||||
$actions->push(new FormAction('next', _t('MultiForm.NEXT', 'Next')));
|
||||
}
|
||||
|
||||
// If there is a previous step defined, add the back button
|
||||
if($step->getPreviousStep() && $step->canGoBack()) {
|
||||
// If there is a next step, insert the action before the next action
|
||||
if($step->getNextStep()) {
|
||||
$actions->insertBefore(new FormAction('prev', _t('MultiForm.BACK', 'Back')), 'action_next');
|
||||
// Assume that this is the last step, insert the action before the finish action
|
||||
} else {
|
||||
$actions->insertBefore(new FormAction('prev', _t('MultiForm.BACK', 'Back')), 'action_finish');
|
||||
}
|
||||
}
|
||||
|
||||
// Merge any extra action fields defined on the step
|
||||
$actions->merge($step->getExtraActions());
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a rendered version of this form, with a specific template.
|
||||
* Looks through the step ancestory templates (MultiFormStep, current step
|
||||
* subclass template) to see if one is available to render the form with. If
|
||||
* any of those don't exist, look for a default Form template to render
|
||||
* with instead.
|
||||
*
|
||||
* @return SSViewer object to render the template with
|
||||
*/
|
||||
function forTemplate() {
|
||||
return $this->renderWith(array(
|
||||
$this->getCurrentStep()->class,
|
||||
'MultiFormStep',
|
||||
$this->class,
|
||||
'MultiForm',
|
||||
'Form'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method saves the data on the final step, after submitting.
|
||||
* It should always be overloaded with parent::finish($data, $form)
|
||||
* so you can create your own functionality which handles saving
|
||||
* of all the data collected through each step of the form.
|
||||
*
|
||||
* @param array $data The request data returned from the form
|
||||
* @param object $form The form that the action was called on
|
||||
*/
|
||||
public function finish($data, $form) {
|
||||
if(!$this->getCurrentStep()->isFinalStep()) {
|
||||
Director::redirectBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save the form data for the current step
|
||||
$this->save($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine what to do when the next action is called.
|
||||
*
|
||||
* Saves the current step session data to the database, creates the
|
||||
* new step based on getNextStep() of the current step (or fetches
|
||||
* an existing one), resets the current step to the next step,
|
||||
* then redirects to the newly set step.
|
||||
*
|
||||
* @param array $data The request data returned from the form
|
||||
* @param object $form The form that the action was called on
|
||||
*/
|
||||
public function next($data, $form) {
|
||||
// Get the next step class
|
||||
$nextStepClass = $this->getCurrentStep()->getNextStep();
|
||||
|
||||
if(!$nextStepClass) {
|
||||
Director::redirectBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
// custom validation (use MultiFormStep->getValidator() for built-in functionality)
|
||||
if(!$this->getCurrentStep()->validateStep($data, $form)) {
|
||||
Director::redirectBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save the form data for the current step
|
||||
$this->save($form->getData());
|
||||
|
||||
// Determine whether we can use a step already in the DB, or have to create a new one
|
||||
if(!$nextStep = DataObject::get_one($nextStepClass, "SessionID = {$this->session->ID}")) {
|
||||
$nextStep = new $nextStepClass();
|
||||
$nextStep->SessionID = $this->session->ID;
|
||||
$nextStep->write();
|
||||
}
|
||||
|
||||
// Set the next step found as the current step
|
||||
$this->setCurrentStep($nextStep);
|
||||
|
||||
// Redirect to the next step
|
||||
Director::redirect($nextStep->Link());
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine what to do when the previous action is called.
|
||||
*
|
||||
* Retrieves the previous step class, finds the record for that
|
||||
* class in the DB, and sets the current step to that step found.
|
||||
* Finally, it redirects to that step.
|
||||
*
|
||||
* @param array $data The request data returned from the form
|
||||
* @param object $form The form that the action was called on
|
||||
*/
|
||||
public function prev($data, $form) {
|
||||
// Get the previous step class
|
||||
$prevStepClass = $this->getCurrentStep()->getPreviousStep();
|
||||
|
||||
if(!$prevStepClass && !$this->getCurrentStep()->canGoBack()) {
|
||||
Director::redirectBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save the form data for the current step
|
||||
$this->save($form->getData());
|
||||
|
||||
// Get the previous step of the class instance returned from $currentStep->getPreviousStep()
|
||||
$prevStep = DataObject::get_one($prevStepClass, "SessionID = {$this->session->ID}");
|
||||
|
||||
// Set the current step as the previous step
|
||||
$this->setCurrentStep($prevStep);
|
||||
|
||||
// Redirect to the previous step
|
||||
Director::redirect($prevStep->Link());
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the raw data given back from the form into session.
|
||||
*
|
||||
* Take the submitted form data for the current step, removing
|
||||
* any key => value pairs that shouldn't be saved, then saves
|
||||
* the data into the session.
|
||||
*
|
||||
* @param array $data An array of data to save
|
||||
*/
|
||||
protected function save($data) {
|
||||
$currentStep = $this->getCurrentStep();
|
||||
if(is_array($data)) {
|
||||
foreach($data as $field => $value) {
|
||||
if(in_array($field, $this->stat('ignored_fields'))) {
|
||||
unset($data[$field]);
|
||||
}
|
||||
}
|
||||
$currentStep->saveData($data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ############ Misc ############
|
||||
|
||||
/**
|
||||
* Add the MultiFormSessionID variable to the URL on form submission.
|
||||
* This is a means to persist the session, by adding it's identification
|
||||
* to the URL, which ties it back to this MultiForm instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function FormAction() {
|
||||
$action = parent::FormAction();
|
||||
$action .= (strpos($action, '?')) ? '&' : '?';
|
||||
$action .= "MultiFormSessionID={$this->session->Hash}";
|
||||
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the steps to show in a linear fashion, starting from the
|
||||
* first step. We run {@link getAllStepsRecursive} passing the steps found
|
||||
* by reference to get a listing of the steps.
|
||||
*
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
public function getAllStepsLinear() {
|
||||
$stepsFound = new DataObjectSet();
|
||||
|
||||
$firstStep = DataObject::get_one($this->stat('start_step'), "SessionID = {$this->session->ID}");
|
||||
$templateData = array(
|
||||
'ID' => $firstStep->ID,
|
||||
'ClassName' => $firstStep->class,
|
||||
'Title' => $firstStep->title ? $firstStep->title : $firstStep->class,
|
||||
'SessionID' => $this->session->Hash,
|
||||
'LinkingMode' => ($firstStep->ID == $this->getCurrentStep()->ID) ? 'current' : 'link'
|
||||
);
|
||||
$stepsFound->push(new ArrayData($templateData));
|
||||
|
||||
$this->getAllStepsRecursive($firstStep, $stepsFound);
|
||||
|
||||
return $stepsFound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively run through steps using the getNextStep() method on each step
|
||||
* to determine what the next step is, gathering each step along the way.
|
||||
* We stop on the last step, and return the results.
|
||||
* If a step in the chain was already saved to the database in the current
|
||||
* session, its used - otherwise a singleton of this step is used.
|
||||
* Caution: Doesn't consider branching for steps which aren't in the database yet.
|
||||
*
|
||||
* @param $step Subclass of MultiFormStep to find the next step of
|
||||
* @param $stepsFound $stepsFound DataObjectSet reference, the steps found to call back on
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
protected function getAllStepsRecursive($step, &$stepsFound) {
|
||||
// Find the next step to the current step, the final step has no next step
|
||||
if(!$step->isFinalStep()) {
|
||||
if($step->getNextStep()) {
|
||||
// Is this step in the DB? If it is, we use that
|
||||
if($nextStep = $step->getNextStepFromDatabase()) {
|
||||
$record = array(
|
||||
'ID' => $nextStep->ID,
|
||||
'ClassName' => $nextStep->class,
|
||||
'Title' => $nextStep->title ? $nextStep->title : $nextStep->class,
|
||||
'SessionID' => $this->session->Hash,
|
||||
'LinkingMode' => ($nextStep->ID == $this->getCurrentStep()->ID) ? 'current' : 'link'
|
||||
);
|
||||
} else {
|
||||
// If it's not in the DB, we use a singleton instance of it instead - this step hasn't been accessed yet
|
||||
$nextStep = singleton($step->getNextStep());
|
||||
$record = array(
|
||||
'ClassName' => $nextStep->class,
|
||||
'Title' => $nextStep->title ? $nextStep->title : $nextStep->class
|
||||
);
|
||||
}
|
||||
// Add the array data, and do a callback
|
||||
$stepsFound->push(new ArrayData($record));
|
||||
$this->getAllStepsRecursive($nextStep, $stepsFound);
|
||||
}
|
||||
// Once we've reached the final step, we just return what we've collected
|
||||
} else {
|
||||
return $stepsFound;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of steps already completed (excluding currently started step).
|
||||
* The way we determine a step is complete is to check if it has the Data
|
||||
* field filled out with a serialized value, then we know that the user has
|
||||
* clicked next on the given step, to proceed.
|
||||
*
|
||||
* @TODO Not sure if it's entirely appropriate to check if Data is set as a
|
||||
* way to determine a step is "completed".
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCompletedStepCount() {
|
||||
$steps = DataObject::get('MultiFormStep', "SessionID = {$this->session->ID} && Data IS NOT NULL");
|
||||
return $steps ? $steps->Count() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Total number of steps in the shortest path (only counting straight path without any branching)
|
||||
* The way we determine this is to check if each step has a next_step string variable set. If it's
|
||||
* anything else (like an array, for defining multiple branches) then it gets counted as a single step.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTotalStepCount() {
|
||||
return $this->getAllStepsLinear() ? $this->getAllStepsLinear()->Count() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Percentage of steps completed (excluding currently started step)
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getCompletedPercent() {
|
||||
return (float)$this->CompletedStepCount * 100 / $this->TotalStepCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
59
code/MultiFormObjectDecorator.php
Normal file
59
code/MultiFormObjectDecorator.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Decorate {@link DataObject}s which are required to be saved
|
||||
* to the database directly by a {@link MultiFormStep}.
|
||||
* Only needed for objects which aren't stored in the session,
|
||||
* which is the default.
|
||||
*
|
||||
* This decorator also augments get() requests to the datalayer
|
||||
* by automatically filtering out temporary objects.
|
||||
* You can override this filter by putting the following statement
|
||||
* in your WHERE clause:
|
||||
* `<MyDataObjectClass>`.`MultiFormIsTemporary` = 1
|
||||
*
|
||||
* @package multiform
|
||||
*/
|
||||
class MultiFormObjectDecorator extends DataObjectDecorator {
|
||||
|
||||
public function updateDBFields() {
|
||||
return array(
|
||||
'db' => array(
|
||||
'MultiFormIsTemporary' => 'Boolean',
|
||||
),
|
||||
'has_one' => array(
|
||||
'MultiFormSession' => 'MultiFormSession',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function augmentSQL(SQLQuery &$query) {
|
||||
// If you're querying by ID, ignore the sub-site - this is a bit ugly...
|
||||
if(
|
||||
strpos($query->where[0], ".`ID` = ") === false
|
||||
&& strpos($query->where[0], ".ID = ") === false
|
||||
&& strpos($query->where[0], "ID = ") !== 0
|
||||
&& !$this->wantsTemporary($query)
|
||||
) {
|
||||
$query->where[] = "`{$query->from[0]}`.`MultiFormIsTemporary` = 0";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current query is supposed
|
||||
* to be exempt from the automatic filtering out
|
||||
* of temporary records.
|
||||
*
|
||||
* @param SQLQuery $query
|
||||
* @return boolean
|
||||
*/
|
||||
protected function wantsTemporary($query) {
|
||||
foreach($query->where as $whereClause) {
|
||||
if($whereClause == "`{$query->from[0]}`.`MultiFormIsTemporary` = 1") return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
54
code/MultiFormPurgeTask.php
Normal file
54
code/MultiFormPurgeTask.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Task to clean out all {@link MultiFormSession} objects from the database.
|
||||
*
|
||||
* Setup Instructions:
|
||||
* You need to create an automated task for your system (cronjobs on unix)
|
||||
* which triggers the run() method through cli-script.php:
|
||||
* /your/path/sapphire/cli-script.php MultiFormPurgeTask/run
|
||||
*
|
||||
* @package multiform
|
||||
*/
|
||||
class MultiFormPurgeTask extends DailyTask {
|
||||
|
||||
/**
|
||||
* Days after which sessions expire and
|
||||
* are automatically deleted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $session_expiry_days = 7;
|
||||
|
||||
/**
|
||||
* Run this cron task.
|
||||
*
|
||||
* Go through all MultiFormSession records that
|
||||
* are older than the days specified in $session_expiry_days
|
||||
* and delete them.
|
||||
*/
|
||||
public function run() {
|
||||
$sessions = $this->getExpiredSessions();
|
||||
$delCount = 0;
|
||||
if($sessions) foreach($sessions as $session) {
|
||||
if($session->delete()) $delCount++;
|
||||
}
|
||||
echo $delCount . ' session records deleted that were older than ' . self::$session_expiry_days . ' days.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all MultiFormSession database records that are older than
|
||||
* the days specified in $session_expiry_days
|
||||
*
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
protected function getExpiredSessions() {
|
||||
return DataObject::get(
|
||||
'MultiFormSession',
|
||||
"DATEDIFF(NOW(), `MultiFormSession`.`Created`) > " . self::$session_expiry_days
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
67
code/MultiFormSession.php
Normal file
67
code/MultiFormSession.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Serializes one or more {@link MultiFormStep}s into
|
||||
* a database object.
|
||||
*
|
||||
* MultiFormSession also stores the current step, so that
|
||||
* the {@link MultiForm} and {@link MultiFormStep} classes
|
||||
* know what the current step is.
|
||||
*
|
||||
* @package multiform
|
||||
*/
|
||||
class MultiFormSession extends DataObject {
|
||||
|
||||
static $db = array(
|
||||
'Hash' => 'Varchar(40)', // cryptographic hash identification to this session
|
||||
'IsComplete' => 'Boolean' // flag to determine if this session is marked completed
|
||||
);
|
||||
|
||||
static $has_one = array(
|
||||
'Submitter' => 'Member',
|
||||
'CurrentStep' => 'MultiFormStep'
|
||||
);
|
||||
|
||||
static $has_many = array(
|
||||
'FormSteps' => 'MultiFormStep'
|
||||
);
|
||||
|
||||
/**
|
||||
* Mark this session as completed.
|
||||
*
|
||||
* This sets the flag "IsComplete" to true,
|
||||
* and writes the session back.
|
||||
*/
|
||||
public function markCompleted() {
|
||||
$this->IsComplete = 1;
|
||||
$this->write();
|
||||
}
|
||||
|
||||
/**
|
||||
* These actions are performed when write() is called on this object.
|
||||
*/
|
||||
public function onBeforeWrite() {
|
||||
// save submitter if a Member is logged in
|
||||
$currentMember = Member::currentMember();
|
||||
if(!$this->SubmitterID && $currentMember) $this->SubmitterID = $currentMember->ID;
|
||||
|
||||
parent::onBeforeWrite();
|
||||
}
|
||||
|
||||
/**
|
||||
* These actions are performed when delete() is called on this object.
|
||||
*/
|
||||
public function onBeforeDelete() {
|
||||
// delete dependent form steps and relation
|
||||
$steps = $this->FormSteps();
|
||||
if($steps) foreach($steps as $step) {
|
||||
$steps->remove($step); // @TODO not sure if this is required (does delete() remove the relation too?)
|
||||
$step->delete();
|
||||
}
|
||||
|
||||
parent::onBeforeDelete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
340
code/MultiFormStep.php
Normal file
340
code/MultiFormStep.php
Normal file
@ -0,0 +1,340 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* MultiFormStep controls the behaviour of a single form step in the MultiForm
|
||||
* process. All form steps are required to be subclasses of this class, as it
|
||||
* encapsulates the functionality required for the step to be aware of itself
|
||||
* in the process by knowing what it's next step is, and if applicable, it's previous
|
||||
* step.
|
||||
*
|
||||
* @package multiform
|
||||
*/
|
||||
class MultiFormStep extends DataObject {
|
||||
|
||||
static $db = array(
|
||||
'Data' => 'Text' // stores serialized maps with all session information
|
||||
);
|
||||
|
||||
static $has_one = array(
|
||||
'Session' => 'MultiFormSession'
|
||||
);
|
||||
|
||||
/**
|
||||
* Centerpiece of the flow control for the form.
|
||||
*
|
||||
* If set to a string, you have a linear form flow
|
||||
* If set to an array, you should use {@link getNextStep()}
|
||||
* to enact flow control and branching to different form
|
||||
* steps, most likely based on previously set session data
|
||||
* (e.g. a checkbox field or a dropdown).
|
||||
*
|
||||
* @var array|string
|
||||
*/
|
||||
protected static $next_steps;
|
||||
|
||||
/**
|
||||
* Each {@link MultiForm} subclass needs at least
|
||||
* one step which is marked as the "final" one
|
||||
* and triggers the {@link MultiForm->finish()}
|
||||
* method that wraps up the whole submission.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $is_final_step = false;
|
||||
|
||||
/**
|
||||
* This variable determines whether a user can use
|
||||
* the "back" action from this step.
|
||||
*
|
||||
* @TODO This does not check if the arbitrarily chosen step
|
||||
* using the step indicator is actually a previous step, so
|
||||
* unless you remove the link from the indicator template, or
|
||||
* type in StepID=23 to the address bar you can still go back
|
||||
* using the step indicator.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected static $can_go_back = true;
|
||||
|
||||
/**
|
||||
* Title of this step.
|
||||
*
|
||||
* Used for the step indicator templates.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* Form class that this step is directly related to.
|
||||
*
|
||||
* @var MultiForm subclass
|
||||
*/
|
||||
protected $form;
|
||||
|
||||
/**
|
||||
* Form fields to be rendered with this step.
|
||||
* (Form object is created in {@link MultiForm}.
|
||||
*
|
||||
* This function needs to be implemented on your
|
||||
* subclasses of MultiFormStep.
|
||||
*
|
||||
* @return FieldSet
|
||||
*/
|
||||
public function getFields() {
|
||||
user_error('Please implement getFields on your MultiFormStep subclass', E_USER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional form actions to be added to this step.
|
||||
* (Form object is created in {@link MultiForm}.
|
||||
*
|
||||
* Note: This is optional, and is to be implemented
|
||||
* on your subclasses of MultiFormStep.
|
||||
*
|
||||
* @return FieldSet
|
||||
*/
|
||||
public function getExtraActions() {
|
||||
return new FieldSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a validator specific to this form.
|
||||
* The form is automatically validated in {@link Form->httpSubmission()}.
|
||||
*
|
||||
* @return Validator
|
||||
*/
|
||||
public function getValidator() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor method for $this->title
|
||||
*
|
||||
* @return string Title of this step
|
||||
*/
|
||||
public function getTitle() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a direct link to this step (only works
|
||||
* if you're allowed to skip steps, or this step
|
||||
* has already been saved to the database
|
||||
* for the current {@link MultiFormSession}).
|
||||
*
|
||||
* @return string Relative URL to this step
|
||||
*/
|
||||
public function Link() {
|
||||
return Controller::curr()->Link() . '?MultiFormSessionID=' . $this->Session()->Hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialize stored session data and return it.
|
||||
* This is used for loading data previously saved
|
||||
* in session back into the form.
|
||||
*
|
||||
* You need to overload this method onto your own
|
||||
* step if you require custom loading. An example
|
||||
* would be selective loading specific fields, leaving
|
||||
* others that are not required.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function loadData() {
|
||||
return unserialize($this->Data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the data for this step into session, serializing it first.
|
||||
*
|
||||
* To selectively save fields, instead of it all, this
|
||||
* method would need to be overloaded on your step class.
|
||||
*
|
||||
* @param array $data The processed data from save() on {@link MultiForm}
|
||||
*/
|
||||
public function saveData($data) {
|
||||
$this->Data = serialize($data);
|
||||
$this->write();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the data on this step into an object,
|
||||
* similiar to {@link Form->saveInto()} - by building
|
||||
* a stub form from {@link getFields()}. This is necessary
|
||||
* to trigger each {@link FormField->saveInto()} method
|
||||
* individually, rather than assuming that all data
|
||||
* serialized through {@link saveData()} can be saved
|
||||
* as a simple value outside of the original FormField context.
|
||||
*
|
||||
* @param DataObject $obj
|
||||
*/
|
||||
public function saveInto($obj) {
|
||||
$form = new Form(
|
||||
new Controller(),
|
||||
'Form',
|
||||
$this->getFields(),
|
||||
new FieldSet()
|
||||
);
|
||||
$form->loadDataFrom($this->loadData());
|
||||
$form->saveInto($obj);
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom validation for a step. In most cases, it should be sufficient
|
||||
* to have built-in validation through the {@link Validator} class
|
||||
* on the {@link getValidator()} method.
|
||||
*
|
||||
* Use {@link Form->sessionMessage()} to feed back validation messages
|
||||
* to the user. Please don't redirect from this method,
|
||||
* this is taken care of in {@link next()}.
|
||||
*
|
||||
* @param array $data Request data
|
||||
* @param Form $form
|
||||
* @return boolean Validation success
|
||||
*/
|
||||
public function validateStep($data, $form) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first value of $next_step
|
||||
*
|
||||
* @return String Classname of a {@link MultiFormStep} subclass
|
||||
*/
|
||||
public function getNextStep() {
|
||||
$nextSteps = $this->stat('next_steps');
|
||||
|
||||
// Check if next_steps have been implemented properly if not the final step
|
||||
if(!$this->isFinalStep()) {
|
||||
if(!isset($nextSteps)) user_error('MultiFormStep->getNextStep(): Please define at least one $next_steps on ' . $this->class, E_USER_ERROR);
|
||||
}
|
||||
|
||||
if(is_string($nextSteps)) {
|
||||
return $nextSteps;
|
||||
} elseif(is_array($nextSteps) && count($nextSteps)) {
|
||||
// custom flow control goes here
|
||||
return $nextSteps[0];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next step to the current step in the database.
|
||||
*
|
||||
* This will only return something if you've previously visited
|
||||
* the step ahead of the current step, and then gone back a step.
|
||||
*
|
||||
* @return MultiFormStep|boolean
|
||||
*/
|
||||
public function getNextStepFromDatabase() {
|
||||
if($this->SessionID) {
|
||||
$nextSteps = $this->stat('next_steps');
|
||||
if(is_string($nextSteps)) {
|
||||
return DataObject::get_one($nextSteps, "SessionID = {$this->SessionID}");
|
||||
} elseif(is_array($nextSteps)) {
|
||||
return DataObject::get_one($nextSteps[0], "SessionID = {$this->SessionID}");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor method for self::$next_steps
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function getNextSteps() {
|
||||
return $this->stat('next_steps');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the previous step, if there is one.
|
||||
*
|
||||
* To determine if there is a previous step, we check the database to see if there's
|
||||
* a previous step for this multi form session ID.
|
||||
*
|
||||
* @return String Classname of a {@link MultiFormStep} subclass
|
||||
*/
|
||||
public function getPreviousStep() {
|
||||
$steps = DataObject::get('MultiFormStep', "SessionID = {$this->SessionID}", 'LastEdited DESC');
|
||||
if($steps) {
|
||||
foreach($steps as $step) {
|
||||
if($step->getNextStep()) {
|
||||
if($step->getNextStep() == $this->class) {
|
||||
return $step->class;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the previous step class record from the database.
|
||||
*
|
||||
* This will only return a record if you've previously been on the step.
|
||||
*
|
||||
* @return MultiFormStep subclass
|
||||
*/
|
||||
public function getPreviousStepFromDatabase() {
|
||||
if($prevStepClass = $this->getPreviousStep()) {
|
||||
return DataObject::get_one($prevStepClass, "SessionID = {$this->SessionID}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the form that this step is directly related to.
|
||||
*
|
||||
* @param MultiForm subclass $form
|
||||
*/
|
||||
public function setForm($form) {
|
||||
$this->form = $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Form
|
||||
*/
|
||||
public function getForm() {
|
||||
return $this->form;
|
||||
}
|
||||
|
||||
// ##################### Utility ####################
|
||||
|
||||
/**
|
||||
* Determines whether the user is able to go back using the "action_back"
|
||||
* form action, based on the boolean value of $can_go_back.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canGoBack() {
|
||||
return $this->stat('can_go_back');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this step is the final step in the multi-step process or not,
|
||||
* based on the variable $is_final_step - which must be defined on at least one step.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isFinalStep() {
|
||||
return $this->stat('is_final_step');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the currently viewed step is the current step set in the session.
|
||||
* This assumes you are checking isCurrentStep() against a data record of a MultiFormStep
|
||||
* subclass, otherwise it doesn't work. An example of this is using a singleton instance - it won't
|
||||
* work because there's no data.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isCurrentStep() {
|
||||
return ($this->class == $this->Session()->CurrentStep()->class) ? true : false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
0
lang/_manifest_exclude
Normal file
0
lang/_manifest_exclude
Normal file
31
lang/de_DE.php
Normal file
31
lang/de_DE.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* German (Germany) language pack
|
||||
* @package modules: multiform
|
||||
* @subpackage i18n
|
||||
*/
|
||||
|
||||
i18n::include_locale_file('modules: multiform', 'en_US');
|
||||
|
||||
global $lang;
|
||||
|
||||
if(array_key_exists('de_DE', $lang) && is_array($lang['de_DE'])) {
|
||||
$lang['de_DE'] = array_merge($lang['en_US'], $lang['de_DE']);
|
||||
} else {
|
||||
$lang['de_DE'] = $lang['en_US'];
|
||||
}
|
||||
|
||||
$lang['de_DE']['MultiForm']['BACK'] = 'Zurück';
|
||||
$lang['de_DE']['MultiForm']['NEXT'] = 'Weiter';
|
||||
$lang['de_DE']['MultiForm']['SUBMIT'] = 'Absenden';
|
||||
$lang['de_DE']['MultiFormSession']['db_Hash'] = 'Hash';
|
||||
$lang['de_DE']['MultiFormSession']['db_IsComplete'] = 'Abgeschlossen?';
|
||||
$lang['de_DE']['MultiFormSession']['has_many_FormSteps'] = 'Formularschritte';
|
||||
$lang['de_DE']['MultiFormSession']['plural_name'] = 'Multi-Formulare';
|
||||
$lang['de_DE']['MultiFormSession']['singular_name'] = 'Multi-Formular';
|
||||
$lang['de_DE']['MultiFormStep']['db_Data'] = 'Daten';
|
||||
$lang['de_DE']['MultiFormStep']['plural_name'] = 'Multi-Formular-Schritte';
|
||||
$lang['de_DE']['MultiFormStep']['singular_name'] = 'Multi-Formular-Schritt';
|
||||
|
||||
?>
|
49
lang/en_US.php
Normal file
49
lang/en_US.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
global $lang;
|
||||
|
||||
$lang['en_US']['MultiForm']['BACK'] = 'Back';
|
||||
$lang['en_US']['MultiForm']['NEXT'] = 'Next';
|
||||
$lang['en_US']['MultiForm']['SUBMIT'] = 'Submit';
|
||||
$lang['en_US']['MultiFormSession']['db_Hash'] = array(
|
||||
'Hash',
|
||||
50,
|
||||
'Name of the object property, e.g. used for automatically generating forms'
|
||||
);
|
||||
$lang['en_US']['MultiFormSession']['db_IsComplete'] = array(
|
||||
'IsComplete',
|
||||
50,
|
||||
'Name of the object property, e.g. used for automatically generating forms'
|
||||
);
|
||||
$lang['en_US']['MultiFormSession']['has_many_FormSteps'] = array(
|
||||
'FormSteps',
|
||||
50,
|
||||
'Name of an object relation, e.g. used for automatically generating forms'
|
||||
);
|
||||
$lang['en_US']['MultiFormSession']['plural_name'] = array(
|
||||
'',
|
||||
50,
|
||||
'Pural name of the object, used in dropdowns and to generally identify a collection of this object in the interface'
|
||||
);
|
||||
$lang['en_US']['MultiFormSession']['singular_name'] = array(
|
||||
'',
|
||||
50,
|
||||
'Singular name of the object, used in dropdowns and to generally identify a single object in the interface'
|
||||
);
|
||||
$lang['en_US']['MultiFormStep']['db_Data'] = array(
|
||||
'Data',
|
||||
50,
|
||||
'Name of the object property, e.g. used for automatically generating forms'
|
||||
);
|
||||
$lang['en_US']['MultiFormStep']['plural_name'] = array(
|
||||
'',
|
||||
50,
|
||||
'Pural name of the object, used in dropdowns and to generally identify a collection of this object in the interface'
|
||||
);
|
||||
$lang['en_US']['MultiFormStep']['singular_name'] = array(
|
||||
'',
|
||||
50,
|
||||
'Singular name of the object, used in dropdowns and to generally identify a single object in the interface'
|
||||
);
|
||||
|
||||
?>
|
9
templates/Includes/MultiFormProgressList.ss
Normal file
9
templates/Includes/MultiFormProgressList.ss
Normal file
@ -0,0 +1,9 @@
|
||||
<ul class="stepIndicator current-$CurrentStep.class">
|
||||
<% control AllStepsLinear %>
|
||||
<li class="$ClassName<% if LinkingMode %> $LinkingMode<% end_if %><% if FirstLast %> $FirstLast<% end_if %>">
|
||||
<% if LinkingMode = current %><% else %><% if ID %><a href="{$Top.URLSegment}/?MultiFormSessionID={$SessionID}&StepID={$ID}"><% end_if %><% end_if %>
|
||||
<% if Title %>$Title<% else %>$ClassName<% end_if %>
|
||||
<% if LinkingMode = current %><% else %><% if ID %></a><% end_if %><% end_if %>
|
||||
</li>
|
||||
<% end_control %>
|
||||
</ul>
|
1
templates/Includes/MultiFormProgressPercent.ss
Normal file
1
templates/Includes/MultiFormProgressPercent.ss
Normal file
@ -0,0 +1 @@
|
||||
<p>You've completed {$CompletedPercent.Nice}% ($CompletedStepCount/$TotalStepCount)</p>
|
47
tests/MultiFormSessionTest.php
Normal file
47
tests/MultiFormSessionTest.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
class MultiFormSessionTest extends SapphireTest {
|
||||
|
||||
/**
|
||||
* Set up the instance of MultiFormSession, writing
|
||||
* a record to the database for this test. We persist
|
||||
* the object in our tests by assigning $this->session
|
||||
*/
|
||||
function setUp() {
|
||||
$this->session = new MultiFormSession();
|
||||
$this->session->write();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test generation of a new session.
|
||||
*
|
||||
* @TODO Write some more advanced tests for MultiFormSession.
|
||||
*/
|
||||
function testSessionGeneration() {
|
||||
$this->assertTrue($this->session->ID != 0);
|
||||
$this->assertTrue($this->session->ID > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a MemberID was set on MultiFormSession if
|
||||
* a member is logged in.
|
||||
*/
|
||||
function testMemberLogging() {
|
||||
$session = new MultiFormSession();
|
||||
$session->write();
|
||||
|
||||
if($memberID = Member::currentUserID()) {
|
||||
$this->assertEquals($memberID, $session->SubmitterID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the MultiFormSession record that we created.
|
||||
*/
|
||||
function tearDown() {
|
||||
$this->session->delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
74
tests/MultiFormTest.php
Normal file
74
tests/MultiFormTest.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* MultiFormTest
|
||||
*
|
||||
* @TODO create some behavioural test cases, such as examining what occurs after
|
||||
* submitting the "previous", "next" form actions that a user would normally
|
||||
* be doing.
|
||||
*
|
||||
* For testing purposes, we have some test MultiForm classes:
|
||||
*
|
||||
* - MultiFormTestClass (subclass of MultiForm)
|
||||
* - MultiFormTestStepOne (subclass of MultiFormStep - the first step)
|
||||
* - MultiFormTestStepTwo (subclass of MultiFormStep - the second step)
|
||||
* - MultiFormTestStepThree (subclass of MultiFormStep - the third step)
|
||||
*
|
||||
* These test classes should be used for testing the operation of a "real"
|
||||
* instance of this multiform step system. Also, as a note here: every instance
|
||||
* of MultiFormStep, which is every step in a form, requires a db/build, as it
|
||||
* is a subclass of DataObject. This is a bit of a pain, but it's required for
|
||||
* the database to store the step data for each step, which is very important!
|
||||
*/
|
||||
class MultiFormTest extends SapphireTest {
|
||||
|
||||
/**
|
||||
* Set up the instance of MultiForm, writing a record
|
||||
* to the database for this test. We persist the object
|
||||
* in our tests by assigning $this->getSession()
|
||||
*/
|
||||
function setUp() {
|
||||
$this->form = new MultiFormTestClass(new Controller(), 'Form');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests initialising a new instance of a test class.
|
||||
*
|
||||
* @TODO Write some decent tests! The current assertions are very basic, and are
|
||||
* nowhere near touching on the more advanced concepts of MultiForm, such
|
||||
* as the form actions (prev/next), session handling, and step handling
|
||||
* through {@link MultiFormStep->getPreviousStep()} and
|
||||
* {@link MultiFormStep->getNextStep()} for example.
|
||||
*/
|
||||
function testInitialisingForm() {
|
||||
$this->assertTrue(is_numeric($this->form->getCurrentStep()->ID) && ($this->form->getCurrentStep()->ID > 0));
|
||||
$this->assertTrue(is_numeric($this->form->getSession()->ID) && ($this->form->getSession()->ID > 0));
|
||||
$this->assertEquals('MultiFormTestStepOne', $this->form->getStartStep());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the 2nd step is correct to what we expect it to be.
|
||||
*/
|
||||
function testSecondStep() {
|
||||
$this->assertEquals('MultiFormTestStepTwo', $this->form->getCurrentStep()->getNextStep());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the amount of steps we have has been calculated correctly.
|
||||
*/
|
||||
function testTotalStepCount() {
|
||||
$this->assertEquals(3, $this->form->getAllStepsLinear()->Count());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the session data that was created. Note: This should delete all the
|
||||
* dependencies such as MultiFormStep instances that are related directly to
|
||||
* this session. These directives can be found on {@link MultiFormSession->onBeforeWrite()}
|
||||
*/
|
||||
function tearDown() {
|
||||
$this->form->getSession()->delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
17
tests/code/MultiFormTestClass.php
Normal file
17
tests/code/MultiFormTestClass.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
class MultiFormTestClass extends MultiForm implements TestOnly {
|
||||
|
||||
protected static $start_step = 'MultiFormTestStepOne';
|
||||
|
||||
/**
|
||||
* Accessor method to $start_step
|
||||
* @return string
|
||||
*/
|
||||
function getStartStep() {
|
||||
return $this->stat('start_step');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
17
tests/code/MultiFormTestStepOne.php
Normal file
17
tests/code/MultiFormTestStepOne.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
class MultiFormTestStepOne extends MultiFormStep implements TestOnly {
|
||||
|
||||
protected static $next_steps = 'MultiFormTestStepTwo';
|
||||
|
||||
function getFields() {
|
||||
return new FieldSet(
|
||||
new TextField('FirstName', 'First name'),
|
||||
new TextField('Surname', 'Surname'),
|
||||
new EmailField('Email', 'Email address')
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
15
tests/code/MultiFormTestStepThree.php
Normal file
15
tests/code/MultiFormTestStepThree.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
class MultiFormTestStepThree extends MultiFormStep implements TestOnly {
|
||||
|
||||
protected static $is_final_step = true;
|
||||
|
||||
function getFields() {
|
||||
return new FieldSet(
|
||||
new TextField('Test', 'Anything else you\'d like to tell us?')
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
15
tests/code/MultiFormTestStepTwo.php
Normal file
15
tests/code/MultiFormTestStepTwo.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
class MultiFormTestStepTwo extends MultiFormStep implements TestOnly {
|
||||
|
||||
protected static $next_steps = 'MultiFormTestStepThree';
|
||||
|
||||
function getFields() {
|
||||
return new FieldSet(
|
||||
new TextareaField('Comments', 'Tell us a bit about yourself...')
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
Loading…
x
Reference in New Issue
Block a user