silverstripe-multiform/code/model/MultiFormStep.php

414 lines
11 KiB
PHP
Raw Normal View History

<?php
2008-04-18 00:03:51 +02:00
/**
* MultiFormStep controls the behaviour of a single form step in the MultiForm
2008-04-22 13:03:03 +02:00
* 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.
2015-11-02 01:38:50 +01:00
*
2008-04-18 00:03:51 +02:00
* @package multiform
*/
class MultiFormStep extends DataObject {
2014-08-13 00:11:02 +02:00
private static $db = array(
2008-04-18 00:03:51 +02:00
'Data' => 'Text' // stores serialized maps with all session information
);
2015-11-02 01:38:50 +01:00
2014-08-13 00:11:02 +02:00
private static $has_one = array(
2008-04-18 00:03:51 +02:00
'Session' => 'MultiFormSession'
);
2015-11-02 01:38:50 +01:00
2008-04-18 00:03:51 +02:00
/**
* Centerpiece of the flow control for the form.
2015-11-02 01:38:50 +01:00
*
2008-04-22 13:03:03 +02:00
* 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
2008-04-18 00:03:51 +02:00
* (e.g. a checkbox field or a dropdown).
*
* @var array|string
*/
public static $next_steps;
2015-11-02 01:38:50 +01:00
2008-04-18 00:03:51 +02:00
/**
2008-04-20 02:28:49 +02:00
* Each {@link MultiForm} subclass needs at least
* one step which is marked as the "final" one
2008-04-18 00:03:51 +02:00
* and triggers the {@link MultiForm->finish()}
* method that wraps up the whole submission.
*
* @var boolean
*/
public static $is_final_step = false;
2008-04-18 00:03:51 +02:00
/**
* This variable determines whether a user can use
* the "back" action from this step.
2015-11-02 01:38:50 +01:00
*
* @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;
2015-11-02 01:38:50 +01:00
2008-04-18 00:03:51 +02:00
/**
2008-04-22 13:03:03 +02:00
* Title of this step.
2015-11-02 01:38:50 +01:00
*
2008-04-22 13:03:03 +02:00
* Used for the step indicator templates.
2008-04-18 00:03:51 +02:00
*
* @var string
*/
protected $title;
2015-11-02 01:38:50 +01:00
/**
* Form class that this step is directly related to.
*
* @var MultiForm subclass
*/
protected $form;
2015-11-02 01:38:50 +01:00
/**
* List of additional CSS classes for this step
*
* @var array $extraClasses
*/
protected $extraClasses = array();
2008-04-18 00:03:51 +02:00
/**
2008-04-20 02:28:49 +02:00
* Form fields to be rendered with this step.
2008-04-18 00:03:51 +02:00
* (Form object is created in {@link MultiForm}.
*
2008-04-20 02:28:49 +02:00
* This function needs to be implemented on your
2008-04-22 13:03:03 +02:00
* subclasses of MultiFormStep.
2008-04-18 00:03:51 +02:00
*
* @return FieldList
2008-04-18 00:03:51 +02:00
*/
public function getFields() {
user_error('Please implement getFields on your MultiFormStep subclass', E_USER_ERROR);
}
2015-11-02 01:38:50 +01:00
2008-04-18 00:03:51 +02:00
/**
2008-04-22 13:03:03 +02:00
* Additional form actions to be added to this step.
2008-04-20 02:28:49 +02:00
* (Form object is created in {@link MultiForm}.
*
2008-04-20 02:28:49 +02:00
* Note: This is optional, and is to be implemented
2008-04-22 13:03:03 +02:00
* on your subclasses of MultiFormStep.
*
* @return FieldList
2008-04-18 00:03:51 +02:00
*/
public function getExtraActions() {
return (class_exists('FieldList')) ? new FieldList() : new FieldSet();
2008-04-18 00:03:51 +02:00
}
2015-11-02 01:38:50 +01:00
2008-04-18 00:03:51 +02:00
/**
* Get a validator specific to this form.
* The form is automatically validated in {@link Form->httpSubmission()}.
2015-11-02 01:38:50 +01:00
*
2008-04-18 00:03:51 +02:00
* @return Validator
*/
public function getValidator() {
2008-04-20 02:28:49 +02:00
return false;
2008-04-18 00:03:51 +02:00
}
2015-11-02 01:38:50 +01:00
/**
2008-04-20 02:28:49 +02:00
* Accessor method for $this->title
2015-11-02 01:38:50 +01:00
*
* @return string Title of this step
*/
public function getTitle() {
return $this->title ? $this->title : $this->class;
}
2015-11-02 01:38:50 +01:00
2008-04-18 00:03:51 +02:00
/**
* 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}).
2008-04-18 00:03:51 +02:00
*
* @return string Relative URL to this step
*/
public function Link() {
2015-02-03 16:17:42 +01:00
$form = $this->form;
return Controller::join_links($form->getDisplayLink(), "?{$form->config()->get_var}={$this->Session()->Hash}");
2008-04-18 00:03:51 +02:00
}
/**
* Unserialize stored session data and return it.
2008-04-21 10:54:48 +02:00
* This is used for loading data previously saved
* in session back into the form.
2015-11-02 01:38:50 +01:00
*
2008-04-21 10:54:48 +02:00
* 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.
2015-11-02 01:38:50 +01:00
*
2008-04-21 10:54:48 +02:00
* @return array
2008-04-18 00:03:51 +02:00
*/
public function loadData() {
return ($this->Data && is_string($this->Data)) ? unserialize($this->Data) : array();
2008-04-18 00:03:51 +02:00
}
2015-11-02 01:38:50 +01:00
2008-04-18 00:03:51 +02:00
/**
* Save the data for this step into session, serializing it first.
2015-11-02 01:38:50 +01:00
*
2008-04-21 10:54:48 +02:00
* To selectively save fields, instead of it all, this
* method would need to be overloaded on your step class.
2008-04-18 00:03:51 +02:00
*
2008-04-21 10:54:48 +02:00
* @param array $data The processed data from save() on {@link MultiForm}
2008-04-18 00:03:51 +02:00
*/
public function saveData($data) {
$this->Data = serialize($data);
$this->write();
}
2015-11-02 01:38:50 +01:00
/**
* 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(
Controller::curr(),
'Form',
$this->getFields(),
((class_exists('FieldList')) ? new FieldList() : new FieldSet())
);
$form->loadDataFrom($this->loadData());
$form->saveInto($obj);
return $obj;
}
2015-11-02 01:38:50 +01:00
/**
* 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;
}
2015-11-02 01:38:50 +01:00
2008-04-18 00:03:51 +02:00
/**
* Returns the first value of $next_step
2015-11-02 01:38:50 +01:00
*
2015-11-24 12:08:14 +01:00
* @return string Classname of a {@link MultiFormStep} subclass
2008-04-18 00:03:51 +02:00
*/
public function getNextStep() {
$nextSteps = static::$next_steps;
2008-04-18 00:03:51 +02:00
// 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);
}
2015-11-02 01:38:50 +01:00
2008-04-18 00:03:51 +02:00
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.
2015-11-02 01:38:50 +01:00
*
2008-04-18 00:03:51 +02:00
* This will only return something if you've previously visited
* the step ahead of the current step, and then gone back a step.
2015-11-02 01:38:50 +01:00
*
2008-04-18 00:03:51 +02:00
* @return MultiFormStep|boolean
*/
public function getNextStepFromDatabase() {
if($this->SessionID && is_numeric($this->SessionID)) {
$nextSteps = static::$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;
}
2008-04-18 00:03:51 +02:00
}
}
2015-11-02 01:38:50 +01:00
2008-04-18 00:03:51 +02:00
/**
* Accessor method for self::$next_steps
2015-11-02 01:38:50 +01:00
*
2008-04-22 13:03:03 +02:00
* @return string|array
2008-04-18 00:03:51 +02:00
*/
public function getNextSteps() {
return static::$next_steps;
2008-04-18 00:03:51 +02:00
}
2015-11-02 01:38:50 +01:00
2008-04-18 00:03:51 +02:00
/**
* Returns the previous step, if there is one.
2015-11-02 01:38:50 +01:00
*
2008-04-18 00:03:51 +02:00
* 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.
2015-11-02 01:38:50 +01:00
*
2015-11-24 12:08:14 +01:00
* @return string Classname of a {@link MultiFormStep} subclass
2008-04-18 00:03:51 +02:00
*/
public function getPreviousStep() {
$steps = DataObject::get('MultiFormStep', "\"SessionID\" = {$this->SessionID}", '"LastEdited" DESC');
2008-04-18 00:03:51 +02:00
if($steps) {
foreach($steps as $step) {
$step->setForm($this->form);
2008-04-18 00:03:51 +02:00
if($step->getNextStep()) {
if($step->getNextStep() == $this->class) {
return $step->class;
}
}
}
}
}
2015-11-02 01:38:50 +01:00
/**
2008-04-21 10:54:48 +02:00
* 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}");
}
}
/**
* Get the text to the use on the button to the previous step.
* @return string
*/
public function getPrevText() {
return _t('MultiForm.BACK', 'Back');
}
/**
* Get the text to use on the button to the next step.
* @return string
*/
public function getNextText() {
return _t('MultiForm.NEXT', 'Next');
}
/**
* Get the text to use on the button to submit the form.
* @return string
*/
public function getSubmitText() {
return _t('MultiForm.SUBMIT', 'Submit');
}
/**
* Sets the form that this step is directly related to.
*
* @param MultiForm subclass $form
*/
public function setForm($form) {
$this->form = $form;
}
2015-11-02 01:38:50 +01:00
/**
* @return Form
*/
public function getForm() {
return $this->form;
}
2015-11-02 01:38:50 +01:00
2008-04-18 00:03:51 +02:00
// ##################### Utility ####################
2015-11-02 01:38:50 +01:00
/**
* Determines whether the user is able to go back using the "action_back"
* Determines whether the user is able to go back using the "action_back"
* Determines whether the user is able to go back using the "action_back"
* form action, based on the boolean value of $can_go_back.
2015-11-02 01:38:50 +01:00
*
* @return boolean
*/
public function canGoBack() {
return static::$can_go_back;
}
2015-11-02 01:38:50 +01:00
2008-04-18 00:03:51 +02:00
/**
* Determines whether this step is the final step in the multi-step process or not,
2008-04-22 13:03:03 +02:00
* based on the variable $is_final_step - which must be defined on at least one step.
2008-04-18 00:03:51 +02:00
*
* @return boolean
*/
public function isFinalStep() {
return static::$is_final_step;
2008-04-18 00:03:51 +02:00
}
2015-11-02 01:38:50 +01:00
/**
* 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.
2015-11-02 01:38:50 +01:00
*
* @return boolean
*/
public function isCurrentStep() {
return ($this->class == $this->Session()->CurrentStep()->class) ? true : false;
}
2015-11-02 01:38:50 +01:00
/**
* Add a CSS-class to the step. If needed, multiple classes can be added by delimiting a string with spaces.
*
* @param string $class A string containing a classname or several class names delimited by a space.
* @return MultiFormStep
*/
public function addExtraClass($class) {
// split at white space
$classes = preg_split('/\s+/', $class);
foreach($classes as $class) {
// add classes one by one
$this->extraClasses[$class] = $class;
}
return $this;
}
/**
* Remove a CSS-class from the step. Multiple classes names can be passed through as a space delimited string.
*
* @param string $class
* @return MultiFormStep
*/
public function removeExtraClass($class) {
// split at white space
$classes = preg_split('/\s+/', $class);
foreach ($classes as $class) {
// unset one by one
unset($this->extraClasses[$class]);
}
return $this;
}
/**
* @return string
*/
public function getExtraClasses() {
return join(' ', array_keys($this->extraClasses));
}
}