mirror of
https://github.com/silverstripe/silverstripe-multiform
synced 2024-06-30 00:09:29 +02:00
ENHANCEMENT Added MultiForm->getSavedSteps() and MultiForm->getSavedStepByClass()
ENHANCEMENT Added MultiFormStep->validateStep() for custom validation routines ENHANCEMENT Changed MultiForm->save() to use $form->getData() instead of $data to ensure that all fields are saveable into a DataObject (had trouble with ConfirmedPasswordField returning array instead of string)
This commit is contained in:
parent
228d03592e
commit
7968b09fea
|
@ -54,7 +54,7 @@ abstract class MultiForm extends Form {
|
||||||
'executeForm',
|
'executeForm',
|
||||||
'MultiFormSessionID',
|
'MultiFormSessionID',
|
||||||
'SecurityID'
|
'SecurityID'
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the MultiForm instance.
|
* Start the MultiForm instance.
|
||||||
|
@ -83,9 +83,8 @@ abstract class MultiForm extends Form {
|
||||||
// Set up the actions for the current step
|
// Set up the actions for the current step
|
||||||
$actions = $this->actionsFor($currentStep);
|
$actions = $this->actionsFor($currentStep);
|
||||||
|
|
||||||
// Set up validation (if necessary) {@TODO find a better way instead
|
// Set up validation (if necessary)
|
||||||
// of hardcoding a check for action_prev in order to prevent validation
|
// @todo find a better way instead of hardcoding a check for action_prev in order to prevent validation when hitting the back button
|
||||||
// when hitting the back button
|
|
||||||
$validator = null;
|
$validator = null;
|
||||||
if(empty($_REQUEST['action_prev'])) {
|
if(empty($_REQUEST['action_prev'])) {
|
||||||
if($this->getCurrentStep()->getValidator()) {
|
if($this->getCurrentStep()->getValidator()) {
|
||||||
|
@ -183,8 +182,8 @@ abstract class MultiForm extends Form {
|
||||||
*/
|
*/
|
||||||
protected function setSession() {
|
protected function setSession() {
|
||||||
// If there's a MultiFormSessionID variable set, find that, otherwise create a new session
|
// If there's a MultiFormSessionID variable set, find that, otherwise create a new session
|
||||||
if(isset($_GET['MultiFormSessionID'])) {
|
if(isset($_GET['MultiFormSessionID'])) {
|
||||||
$this->session = $this->getSessionRecord($_GET['MultiFormSessionID']);
|
$this->session = $this->getSessionRecord($_GET['MultiFormSessionID']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there was no session found, create a new one instead
|
// If there was no session found, create a new one instead
|
||||||
|
@ -192,24 +191,59 @@ abstract class MultiForm extends Form {
|
||||||
$this->session = new MultiFormSession();
|
$this->session = new MultiFormSession();
|
||||||
$this->session->write();
|
$this->session->write();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create encrypted identification to the session instance if it doesn't exist
|
// Create encrypted identification to the session instance if it doesn't exist
|
||||||
if(!$this->session->Hash) {
|
if(!$this->session->Hash) {
|
||||||
$this->session->Hash = sha1($this->session->ID . '-' . microtime());
|
$this->session->Hash = sha1($this->session->ID . '-' . microtime());
|
||||||
$this->session->write();
|
$this->session->write();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an instance of MultiFormSession.
|
* Return an instance of MultiFormSession.
|
||||||
*
|
*
|
||||||
* @param string $hash The unique, encrypted hash to identify the session
|
* @param string $hash The unique, encrypted hash to identify the session
|
||||||
* @return MultiFormSession
|
* @return MultiFormSession
|
||||||
*/
|
*/
|
||||||
function getSessionRecord($hash) {
|
function getSessionRecord($hash) {
|
||||||
$SQL_hash = Convert::raw2sql($hash);
|
$SQL_hash = Convert::raw2sql($hash);
|
||||||
return DataObject::get_one('MultiFormSession', "Hash = '$SQL_hash' AND IsComplete = 0");
|
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.
|
* Build a FieldSet of the FormAction fields for the given step.
|
||||||
|
@ -241,7 +275,7 @@ abstract class MultiForm extends Form {
|
||||||
} else {
|
} else {
|
||||||
$actions->push(new FormAction('next', _t('MultiForm.NEXT', 'Next')));
|
$actions->push(new FormAction('next', _t('MultiForm.NEXT', 'Next')));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is a previous step defined, add the back button
|
// If there is a previous step defined, add the back button
|
||||||
if($step->getPreviousStep() && $step->canGoBack()) {
|
if($step->getPreviousStep() && $step->canGoBack()) {
|
||||||
// If there is a next step, insert the action before the next action
|
// If there is a next step, insert the action before the next action
|
||||||
|
@ -284,16 +318,16 @@ abstract class MultiForm extends Form {
|
||||||
* so you can create your own functionality which handles saving
|
* so you can create your own functionality which handles saving
|
||||||
* of all the data collected through each step of the form.
|
* of all the data collected through each step of the form.
|
||||||
*
|
*
|
||||||
* @param array $data The request data returned from the form
|
* @param array $data The request data returned from the form
|
||||||
* @param object $form The form that the action was called on
|
* @param object $form The form that the action was called on
|
||||||
*/
|
*/
|
||||||
public function finish($data, $form) {
|
public function finish($data, $form) {
|
||||||
if(!$this->getCurrentStep()->isFinalStep()) {
|
if(!$this->getCurrentStep()->isFinalStep()) {
|
||||||
Director::redirectBack();
|
Director::redirectBack();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the form data for the current step
|
// Save the form data for the current step
|
||||||
$this->save($data);
|
$this->save($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,24 +337,30 @@ abstract class MultiForm extends Form {
|
||||||
* Saves the current step session data to the database, creates the
|
* Saves the current step session data to the database, creates the
|
||||||
* new step based on getNextStep() of the current step (or fetches
|
* new step based on getNextStep() of the current step (or fetches
|
||||||
* an existing one), resets the current step to the next step,
|
* an existing one), resets the current step to the next step,
|
||||||
* then redirects to the newly set step.
|
* then redirects to the newly set step.
|
||||||
*
|
*
|
||||||
* @param array $data The request data returned from the form
|
* @param array $data The request data returned from the form
|
||||||
* @param object $form The form that the action was called on
|
* @param object $form The form that the action was called on
|
||||||
*/
|
*/
|
||||||
public function next($data, $form) {
|
public function next($data, $form) {
|
||||||
if(!$this->getCurrentStep()->getNextStep()) {
|
// 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();
|
Director::redirectBack();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the next step class
|
|
||||||
$nextStepClass = $this->getCurrentStep()->getNextStep();
|
|
||||||
|
|
||||||
// Save the form data for the current step
|
// Save the form data for the current step
|
||||||
$this->save($data);
|
$this->save($form->getData());
|
||||||
|
|
||||||
// Determine whether we can use a step already in the DB, or have to create a new one
|
// 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}")) {
|
if(!$nextStep = DataObject::get_one($nextStepClass, "SessionID = {$this->session->ID}")) {
|
||||||
$nextStep = new $nextStepClass();
|
$nextStep = new $nextStepClass();
|
||||||
$nextStep->SessionID = $this->session->ID;
|
$nextStep->SessionID = $this->session->ID;
|
||||||
|
@ -340,9 +380,9 @@ abstract class MultiForm extends Form {
|
||||||
*
|
*
|
||||||
* Retrieves the previous step class, finds the record for that
|
* Retrieves the previous step class, finds the record for that
|
||||||
* class in the DB, and sets the current step to that step found.
|
* class in the DB, and sets the current step to that step found.
|
||||||
* Finally, it redirects to that step.
|
* Finally, it redirects to that step.
|
||||||
*
|
*
|
||||||
* @param array $data The request data returned from the form
|
* @param array $data The request data returned from the form
|
||||||
* @param object $form The form that the action was called on
|
* @param object $form The form that the action was called on
|
||||||
*/
|
*/
|
||||||
public function prev($data, $form) {
|
public function prev($data, $form) {
|
||||||
|
@ -381,7 +421,7 @@ abstract class MultiForm extends Form {
|
||||||
$currentStep = $this->getCurrentStep();
|
$currentStep = $this->getCurrentStep();
|
||||||
if(is_array($data)) {
|
if(is_array($data)) {
|
||||||
foreach($data as $field => $value) {
|
foreach($data as $field => $value) {
|
||||||
if(in_array($field, $this->stat('ignored_fields')) || self::is_action_field($field)) {
|
if(in_array($field, $this->stat('ignored_fields'))) {
|
||||||
unset($data[$field]);
|
unset($data[$field]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -395,8 +435,8 @@ abstract class MultiForm extends Form {
|
||||||
/**
|
/**
|
||||||
* Add the MultiFormSessionID variable to the URL on form submission.
|
* Add the MultiFormSessionID variable to the URL on form submission.
|
||||||
* This is a means to persist the session, by adding it's identification
|
* This is a means to persist the session, by adding it's identification
|
||||||
* to the URL, which ties it back to this MultiForm instance.
|
* to the URL, which ties it back to this MultiForm instance.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function FormAction() {
|
function FormAction() {
|
||||||
|
@ -407,13 +447,13 @@ abstract class MultiForm extends Form {
|
||||||
return $action;
|
return $action;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the steps to show in a linear fashion, starting from the
|
* Determine the steps to show in a linear fashion, starting from the
|
||||||
* first step. We run a recursive function passing the steps found
|
* first step. We run {@link getAllStepsRecursive} passing the steps found
|
||||||
* by reference to get a listing of the steps.
|
* by reference to get a listing of the steps.
|
||||||
*
|
*
|
||||||
* @return DataObjectSet
|
* @return DataObjectSet
|
||||||
*/
|
*/
|
||||||
public function getAllStepsLinear() {
|
public function getAllStepsLinear() {
|
||||||
$stepsFound = new DataObjectSet();
|
$stepsFound = new DataObjectSet();
|
||||||
|
|
||||||
|
@ -436,6 +476,9 @@ abstract class MultiForm extends Form {
|
||||||
* Recursively run through steps using the getNextStep() method on each step
|
* Recursively run through steps using the getNextStep() method on each step
|
||||||
* to determine what the next step is, gathering each step along the way.
|
* to determine what the next step is, gathering each step along the way.
|
||||||
* We stop on the last step, and return the results.
|
* 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 $step Subclass of MultiFormStep to find the next step of
|
||||||
* @param $stepsFound $stepsFound DataObjectSet reference, the steps found to call back on
|
* @param $stepsFound $stepsFound DataObjectSet reference, the steps found to call back on
|
||||||
|
@ -470,7 +513,7 @@ abstract class MultiForm extends Form {
|
||||||
} else {
|
} else {
|
||||||
return $stepsFound;
|
return $stepsFound;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of steps already completed (excluding currently started step).
|
* Number of steps already completed (excluding currently started step).
|
||||||
|
@ -506,26 +549,26 @@ abstract class MultiForm extends Form {
|
||||||
*/
|
*/
|
||||||
public function getCompletedPercent() {
|
public function getCompletedPercent() {
|
||||||
return (float)$this->CompletedStepCount * 100 / $this->TotalStepCount;
|
return (float)$this->CompletedStepCount * 100 / $this->TotalStepCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the field is an action. This checks the string name of the
|
* Determines whether the field is an action. This checks the string name of the
|
||||||
* field, and not the actual field object of one. The actual checking is done
|
* field, and not the actual field object of one. The actual checking is done
|
||||||
* by doing a string check to see if "action_" is prefixed to the name of the
|
* by doing a string check to see if "action_" is prefixed to the name of the
|
||||||
* field. For example, in the form system: FormAction('next', 'Next') field
|
* field. For example, in the form system: FormAction('next', 'Next') field
|
||||||
* gives an ID of "action_next"
|
* gives an ID of "action_next"
|
||||||
*
|
*
|
||||||
* The assumption here is the ID we're checking against has the prefix that we're
|
* The assumption here is the ID we're checking against has the prefix that we're
|
||||||
* looking for, otherwise this won't work.
|
* looking for, otherwise this won't work.
|
||||||
*
|
*
|
||||||
* @param string $fieldName The name of the field to check is an action
|
* @param string $fieldName The name of the field to check is an action
|
||||||
* @param string $prefix The prefix of the string to check for, default is "action_"
|
* @param string $prefix The prefix of the string to check for, default is "action_"
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public static function is_action_field($fieldName, $prefix = 'action_') {
|
public static function is_action_field($fieldName, $prefix = 'action_') {
|
||||||
if(substr((string)$fieldName, 0, strlen($prefix)) == $prefix) return true;
|
if(substr((string)$fieldName, 0, strlen($prefix)) == $prefix) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MultiFormStep controls the behaviour of a single form step in the MultiForm
|
* 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
|
* 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
|
* 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
|
* in the process by knowing what it's next step is, and if applicable, it's previous
|
||||||
|
@ -100,21 +100,22 @@ class MultiFormStep extends DataObject {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a validator specific to this form.
|
* Get a validator specific to this form.
|
||||||
|
* The form is automatically validated in {@link Form->httpSubmission()}.
|
||||||
*
|
*
|
||||||
* @return Validator
|
* @return Validator
|
||||||
*/
|
*/
|
||||||
public function getValidator() {
|
public function getValidator() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accessor method for $this->title
|
* Accessor method for $this->title
|
||||||
*
|
*
|
||||||
* @return string Title of this step
|
* @return string Title of this step
|
||||||
*/
|
*/
|
||||||
public function getTitle() {
|
public function getTitle() {
|
||||||
return $this->title;
|
return $this->title;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a direct link to this step (only works
|
* Gets a direct link to this step (only works
|
||||||
|
@ -157,6 +158,24 @@ class MultiFormStep extends DataObject {
|
||||||
$this->write();
|
$this->write();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()}.
|
||||||
|
*
|
||||||
|
* @usedby 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
|
* Returns the first value of $next_step
|
||||||
*
|
*
|
||||||
|
@ -274,19 +293,19 @@ class MultiFormStep extends DataObject {
|
||||||
public function isFinalStep() {
|
public function isFinalStep() {
|
||||||
return $this->stat('is_final_step');
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
?>
|
?>
|
Loading…
Reference in New Issue
Block a user