FIX ran linter and removed php tags from README

This commit is contained in:
Franco Springveldt 2017-09-07 08:47:35 +12:00
parent b9c4929fd5
commit 43cc780627
35 changed files with 1797 additions and 1772 deletions

431
README.md
View File

@ -83,7 +83,7 @@ Using [Composer](https://getcomposer.org/), you can install multiform into your
SilverStripe site using this command (while in the directory where your site is SilverStripe site using this command (while in the directory where your site is
currently located) currently located)
composer require "silverstripe/multiform:*" `composer require "silverstripe/multiform:*"`
### 2. Create subclass of MultiForm ### 2. Create subclass of MultiForm
@ -91,13 +91,11 @@ First of all, we need to create a new subclass of *MultiForm*.
For the above example, our multi-form will be called *SurveyForm* For the above example, our multi-form will be called *SurveyForm*
:::php ```php
<?php class SurveyForm extends MultiForm {
class SurveyForm extends MultiForm {
}
}
```
### 3. Set up first step ### 3. Set up first step
@ -111,27 +109,23 @@ form.
So, for example, if we were going to have a first step which collects the So, for example, if we were going to have a first step which collects the
personal details of the form user, then we might have this class: personal details of the form user, then we might have this class:
:::php ```php
<?php class SurveyFormPersonalDetailsStep extends MultiFormStep {
class SurveyFormPersonalDetailsStep extends MultiFormStep {
}
}
```
Now that we've got our first step of the form defined, we need to go back to our Now that we've got our first step of the form defined, we need to go back to our
subclass of MultiForm, SurveyForm, and tell it that SurveyFormPersonalDetailsStep subclass of MultiForm, SurveyForm, and tell it that SurveyFormPersonalDetailsStep
is the first step. is the first step.
:::php ```php
<?php class SurveyForm extends MultiForm {
class SurveyForm extends MultiForm { public static $start_step = 'SurveyFormPersonalDetailsStep';
public static $start_step = 'SurveyFormPersonalDetailsStep';
}
}
```
### 4. Define next step, and final step ### 4. Define next step, and final step
@ -144,22 +138,20 @@ order to use flow control in our system.
To let the step know what step is next in the process, we do the same as setting To let the step know what step is next in the process, we do the same as setting
the `$start_step` variable *SurveyForm*, but we call it `$next_steps`. the `$start_step` variable *SurveyForm*, but we call it `$next_steps`.
:::php ```php
<?php class SurveyFormPersonalDetailsStep extends MultiFormStep {
class SurveyFormPersonalDetailsStep extends MultiFormStep { public static $next_steps = 'SurveyFormOrganisationDetailsStep';
public static $next_steps = 'SurveyFormOrganisationDetailsStep'; public function getFields() {
return new FieldList(
public function getFields() { new TextField('FirstName', 'First name'),
return new FieldList( new TextField('Surname', 'Surname')
new TextField('FirstName', 'First name'), );
new TextField('Surname', 'Surname') }
);
}
}
}
```
At the very least, each step also has to have a `getFields()` method returning At the very least, each step also has to have a `getFields()` method returning
a *FieldSet* with some form field objects. These are the fields that the form a *FieldSet* with some form field objects. These are the fields that the form
@ -172,15 +164,13 @@ this is the final step.
So, if we assume that the last step in our process is So, if we assume that the last step in our process is
SurveyFormOrganisationDetailsStep, then we can do something like this: SurveyFormOrganisationDetailsStep, then we can do something like this:
:::php ```php
<?php class SurveyFormOrganisationDetailsStep extends MultiFormStep {
class SurveyFormOrganisationDetailsStep extends MultiFormStep { public static $is_final_step = true;
public static $is_final_step = true;
}
}
```
### 5. Run database integrity check ### 5. Run database integrity check
@ -197,40 +187,38 @@ that the form can be rendered into a given template.
So, if we want to render our multi-form as `$SurveyForm` in the *Page.ss* So, if we want to render our multi-form as `$SurveyForm` in the *Page.ss*
template, we need to create a SurveyForm method (function) on the controller: template, we need to create a SurveyForm method (function) on the controller:
:::php ```php
<?php class Page extends SiteTree {
class Page extends SiteTree { // ...
// ... }
} class Page_Controller extends ContentController {
class Page_Controller extends ContentController { // ...
// ... //
private static $allowed_actions = array(
'SurveyForm',
'finished'
);
// public function SurveyForm() {
private static $allowed_actions = array( return new SurveyForm($this, 'Form');
'SurveyForm', }
'finished'
);
public function SurveyForm() { public function finished() {
return new SurveyForm($this, 'Form'); return array(
} 'Title' => 'Thank you for your submission',
'Content' => '<p>You have successfully submitted the form!</p>'
);
}
public function finished() { // ...
return array(
'Title' => 'Thank you for your submission',
'Content' => '<p>You have successfully submitted the form!</p>'
);
}
// ...
}
}
```
The `SurveyForm()` function will create a new instance our subclass of The `SurveyForm()` function will create a new instance our subclass of
MultiForm, which in this example, is *SurveyForm*. This in turn will then set MultiForm, which in this example, is *SurveyForm*. This in turn will then set
@ -242,21 +230,21 @@ like.
Your template should look something like this, to render the form in: Your template should look something like this, to render the form in:
:::html ```html
<div id="content"> <div id="content">
<% if $Content %> <% if $Content %>
$Content $Content
<% end_if %> <% end_if %>
<% if $SurveyForm %> <% if $SurveyForm %>
$SurveyForm $SurveyForm
<% end_if %> <% end_if %>
<% if $Form %>
$Form
<% end_if %>
</div>
<% if $Form %>
$Form
<% end_if %>
</div>
```
In this case, the above template example is a *sub-template* inside the *Layout* In this case, the above template example is a *sub-template* inside the *Layout*
directory for the templates. Note that we have also included `$Form`, so directory for the templates. Note that we have also included `$Form`, so
@ -281,11 +269,11 @@ To include these with our instance of multiform, we just need to add an
For example: For example:
:::html ```html
<% with $SurveyForm %> <% with $SurveyForm %>
<% include MultiFormProgressList %> <% include MultiFormProgressList %>
<% end_with %> <% end_with %>
```
This means the included template is rendered within the scope of the This means the included template is rendered within the scope of the
SurveyForm instance returned, instead of the top level controller context. SurveyForm instance returned, instead of the top level controller context.
@ -293,24 +281,24 @@ This gives us the data to show the progression of the steps.
Putting it together, we might have something looking like this: Putting it together, we might have something looking like this:
:::html ```html
<div id="content"> <div id="content">
<% if $Content %> <% if $Content %>
$Content $Content
<% end_if %> <% end_if %>
<% if $SurveyForm %> <% if $SurveyForm %>
<% with $SurveyForm %> <% with $SurveyForm %>
<% include MultiFormProgressList %> <% include MultiFormProgressList %>
<% end_with %> <% end_with %>
$SurveyForm
<% end_if %>
<% if $Form %>
$Form
<% end_if %>
</div>
$SurveyForm
<% end_if %>
<% if $Form %>
$Form
<% end_if %>
</div>
```
Feel free to play around with the progress indicators. If you need something Feel free to play around with the progress indicators. If you need something
specific to your project, just create a new "Include" template inside your own specific to your project, just create a new "Include" template inside your own
@ -343,37 +331,35 @@ based on the submission value of another step. There are two methods supporting
Here is an example of how to populate the email address from step 1 in step2 : Here is an example of how to populate the email address from step 1 in step2 :
:::php ```php
<?php class Step1 extends MultiFormStep
{
public static $next_steps = 'Step2';
class Step1 extends MultiFormStep public function getFields() {
{ return new FieldList(
public static $next_steps = 'Step2'; new EmailField('Email', 'Your email')
);
}
}
public function getFields() { class Step2 extends MultiFormStep
return new FieldList( {
new EmailField('Email', 'Your email') public static $next_steps = 'Step3';
);
}
}
class Step2 extends MultiFormStep public function getFields() {
{ $fields = new FieldList(
public static $next_steps = 'Step3'; new EmailField('Email', 'E-mail'),
new EmailField('Email2', 'Verify E-Mail')
);
public function getFields() { // set the email field to the input from Step 1
$fields = new FieldList( $this->copyValueFromOtherStep($fields, 'Step1', 'Email');
new EmailField('Email', 'E-mail'),
new EmailField('Email2', 'Verify E-Mail')
);
// set the email field to the input from Step 1
$this->copyValueFromOtherStep($fields, 'Step1', 'Email');
return $fields;
}
}
return $fields;
}
}
```
### 8. Finishing it up ### 8. Finishing it up
@ -389,56 +375,54 @@ So, we must write some code on our subclass of *MultiForm*, overloading
Here is an example of what we could do here: Here is an example of what we could do here:
:::php ```php
<?php class SurveyForm extends MultiForm {
class SurveyForm extends MultiForm { public static $start_step = 'SurveyFormPersonalDetailsStep';
public static $start_step = 'SurveyFormPersonalDetailsStep'; public function finish($data, $form) {
parent::finish($data, $form);
public function finish($data, $form) { $steps = DataObject::get(
parent::finish($data, $form); 'MultiFormStep',
"SessionID = {$this->session->ID}"
);
$steps = DataObject::get( if($steps) {
'MultiFormStep', foreach($steps as $step) {
"SessionID = {$this->session->ID}" if($step->class == 'SurveyFormPersonalDetailsStep') {
); $member = new Member();
$data = $step->loadData();
if($steps) { if($data) {
foreach($steps as $step) { $member->update($data);
if($step->class == 'SurveyFormPersonalDetailsStep') { $member->write();
$member = new Member(); }
$data = $step->loadData(); }
if($data) { if($step->class == 'SurveyOrganisationDetailsStep') {
$member->update($data); $organisation = new Organisation();
$member->write(); $data = $step->loadData();
}
}
if($step->class == 'SurveyOrganisationDetailsStep') { if($data) {
$organisation = new Organisation(); $organisation->update($data);
$data = $step->loadData();
if($data) { if($member && $member->ID) {
$organisation->update($data); $organisation->MemberID = $member->ID;
}
if($member && $member->ID) { $organisation->write();
$organisation->MemberID = $member->ID; }
} }
// Shows the step data (unserialized by loadData)
$organisation->write(); // Debug::show($step->loadData());
} }
} }
// Shows the step data (unserialized by loadData)
// Debug::show($step->loadData());
}
}
$this->controller->redirect($this->controller->Link() . 'finished');
}
}
$this->controller->redirect($this->controller->Link() . 'finished');
}
}
```
#### 9. Organisation data model #### 9. Organisation data model
@ -449,17 +433,14 @@ groups in SilverStripe) so we need to create it:
This example has been chosen as a separate DataObject but you may wish to change This example has been chosen as a separate DataObject but you may wish to change
the code and add the data to the Member class instead. the code and add the data to the Member class instead.
:::php ```php
<?php class Organisation extends DataObject {
class Organisation extends DataObject {
private static $db = array(
// Add your Organisation fields here
);
}
private static $db = array(
// Add your Organisation fields here
);
}
```
#### Warning #### Warning
If you're dealing with sensitive data, it's best to delete the session and step If you're dealing with sensitive data, it's best to delete the session and step
@ -468,9 +449,9 @@ data immediately after the form is successfully submitted.
You can delete it by calling this method on the finish() for your MultiForm You can delete it by calling this method on the finish() for your MultiForm
subclass: subclass:
:::php ```php
$this->session->delete(); $this->session->delete();
```
This will also go through each of it's steps and delete them as well. This will also go through each of it's steps and delete them as well.
@ -517,24 +498,24 @@ be something different based on a user's choice of input during the step, you
can override getNextStep() on any given step to manually override what the next can override getNextStep() on any given step to manually override what the next
step should be. An example: step should be. An example:
:::php ```php
class MyStep extends MultiFormStep class MyStep extends MultiFormStep
// ... // ...
public function getNextStep() { public function getNextStep() {
$data = $this->loadData(); $data = $this->loadData();
if(@$data['Gender'] == 'Male') { if(@$data['Gender'] == 'Male') {
return 'TestThirdCase1Step'; return 'TestThirdCase1Step';
} else { } else {
return 'TestThirdCase2Step'; return 'TestThirdCase2Step';
} }
} }
// ... // ...
}
}
```
### Validation ### Validation
To define validation on a step-by-step basis, please define getValidator() and To define validation on a step-by-step basis, please define getValidator() and
@ -543,22 +524,22 @@ validation see [:form](http://doc.silverstripe.org/form-validation).
e.g. e.g.
:::php ```php
class MyStep extends MultiFormStep { class MyStep extends MultiFormStep {
... ...
public function getValidator() { public function getValidator() {
return new RequiredFields(array( return new RequiredFields(array(
'Name', 'Name',
'Email' 'Email'
)); ));
} }
... ...
}
}
```
### finish() ### finish()
@ -573,29 +554,27 @@ won't be saved.
For example: For example:
:::php ```php
<?php class SurveyForm extends MultiForm {
class SurveyForm extends MultiForm { public static $start_step = 'SurveyFormPersonalDetailsStep';
public static $start_step = 'SurveyFormPersonalDetailsStep'; public function finish($data, $form) {
parent::finish($data, $form);
public function finish($data, $form) { $steps = MultiFormStep::get()->filter(array(
parent::finish($data, $form); "SessionID" => $this->session->ID
));
$steps = MultiFormStep::get()->filter(array(
"SessionID" => $this->session->ID
));
if($steps) {
foreach($steps as $step) {
// Shows the step data (unserialized by loadData)
Debug::show($step->loadData());
}
}
}
}
if($steps) {
foreach($steps as $step) {
// Shows the step data (unserialized by loadData)
Debug::show($step->loadData());
}
}
}
}
```
The above is a sample bit of code that simply fetches all the steps in the The above is a sample bit of code that simply fetches all the steps in the
database that were saved. Further refinement could include getting steps only database that were saved. Further refinement could include getting steps only
@ -613,9 +592,9 @@ idea to immediately delete this data after the user has submitted.
This can be easily achieved by adding the following line at the end of your This can be easily achieved by adding the following line at the end of your
`finish()` method on your MultiForm subclass. `finish()` method on your MultiForm subclass.
:::php ```php
$this->session->delete(); $this->session->delete();
```
### Expiring old session data ### Expiring old session data

View File

@ -1,2 +1 @@
<?php <?php

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('ar_SA', $lang) && is_array($lang['ar_SA'])) { if (array_key_exists('ar_SA', $lang) && is_array($lang['ar_SA'])) {
$lang['ar_SA'] = array_merge($lang['en_US'], $lang['ar_SA']); $lang['ar_SA'] = array_merge($lang['en_US'], $lang['ar_SA']);
} else { } else {
$lang['ar_SA'] = $lang['en_US']; $lang['ar_SA'] = $lang['en_US'];
} }
$lang['ar_SA']['MultiForm']['BACK'] = 'السابق'; $lang['ar_SA']['MultiForm']['BACK'] = 'السابق';
@ -27,5 +27,3 @@ $lang['ar_SA']['MultiFormSession']['singular_name'] = '(لايوجد)';
$lang['ar_SA']['MultiFormStep']['db_Data'] = 'بيانات'; $lang['ar_SA']['MultiFormStep']['db_Data'] = 'بيانات';
$lang['ar_SA']['MultiFormStep']['plural_name'] = '(لايوجد)'; $lang['ar_SA']['MultiFormStep']['plural_name'] = '(لايوجد)';
$lang['ar_SA']['MultiFormStep']['singular_name'] = '(لايوجد)'; $lang['ar_SA']['MultiFormStep']['singular_name'] = '(لايوجد)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('bg_BG', $lang) && is_array($lang['bg_BG'])) { if (array_key_exists('bg_BG', $lang) && is_array($lang['bg_BG'])) {
$lang['bg_BG'] = array_merge($lang['en_US'], $lang['bg_BG']); $lang['bg_BG'] = array_merge($lang['en_US'], $lang['bg_BG']);
} else { } else {
$lang['bg_BG'] = $lang['en_US']; $lang['bg_BG'] = $lang['en_US'];
} }
$lang['bg_BG']['MultiForm']['BACK'] = 'Назад'; $lang['bg_BG']['MultiForm']['BACK'] = 'Назад';
@ -23,5 +23,3 @@ $lang['bg_BG']['MultiFormSession']['plural_name'] = '(никакви)';
$lang['bg_BG']['MultiFormSession']['singular_name'] = '(никакво)'; $lang['bg_BG']['MultiFormSession']['singular_name'] = '(никакво)';
$lang['bg_BG']['MultiFormStep']['plural_name'] = '(никакви)'; $lang['bg_BG']['MultiFormStep']['plural_name'] = '(никакви)';
$lang['bg_BG']['MultiFormStep']['singular_name'] = '(никакво)'; $lang['bg_BG']['MultiFormStep']['singular_name'] = '(никакво)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('bs_BA', $lang) && is_array($lang['bs_BA'])) { if (array_key_exists('bs_BA', $lang) && is_array($lang['bs_BA'])) {
$lang['bs_BA'] = array_merge($lang['en_US'], $lang['bs_BA']); $lang['bs_BA'] = array_merge($lang['en_US'], $lang['bs_BA']);
} else { } else {
$lang['bs_BA'] = $lang['en_US']; $lang['bs_BA'] = $lang['en_US'];
} }
$lang['bs_BA']['MultiFormSession']['db_Hash'] = 'Hash'; $lang['bs_BA']['MultiFormSession']['db_Hash'] = 'Hash';
@ -24,5 +24,3 @@ $lang['bs_BA']['MultiFormSession']['singular_name'] = '(ništa)';
$lang['bs_BA']['MultiFormStep']['db_Data'] = 'Podaci'; $lang['bs_BA']['MultiFormStep']['db_Data'] = 'Podaci';
$lang['bs_BA']['MultiFormStep']['plural_name'] = '(ništa)'; $lang['bs_BA']['MultiFormStep']['plural_name'] = '(ništa)';
$lang['bs_BA']['MultiFormStep']['singular_name'] = '(ništa)'; $lang['bs_BA']['MultiFormStep']['singular_name'] = '(ništa)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('cs_CZ', $lang) && is_array($lang['cs_CZ'])) { if (array_key_exists('cs_CZ', $lang) && is_array($lang['cs_CZ'])) {
$lang['cs_CZ'] = array_merge($lang['en_US'], $lang['cs_CZ']); $lang['cs_CZ'] = array_merge($lang['en_US'], $lang['cs_CZ']);
} else { } else {
$lang['cs_CZ'] = $lang['en_US']; $lang['cs_CZ'] = $lang['en_US'];
} }
$lang['cs_CZ']['MultiForm']['BACK'] = 'Zpět'; $lang['cs_CZ']['MultiForm']['BACK'] = 'Zpět';
@ -27,5 +27,3 @@ $lang['cs_CZ']['MultiFormSession']['singular_name'] = '(žádný)';
$lang['cs_CZ']['MultiFormStep']['db_Data'] = 'Data'; $lang['cs_CZ']['MultiFormStep']['db_Data'] = 'Data';
$lang['cs_CZ']['MultiFormStep']['plural_name'] = '(žádný)'; $lang['cs_CZ']['MultiFormStep']['plural_name'] = '(žádný)';
$lang['cs_CZ']['MultiFormStep']['singular_name'] = '(žádný)'; $lang['cs_CZ']['MultiFormStep']['singular_name'] = '(žádný)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('da_DK', $lang) && is_array($lang['da_DK'])) { if (array_key_exists('da_DK', $lang) && is_array($lang['da_DK'])) {
$lang['da_DK'] = array_merge($lang['en_US'], $lang['da_DK']); $lang['da_DK'] = array_merge($lang['en_US'], $lang['da_DK']);
} else { } else {
$lang['da_DK'] = $lang['en_US']; $lang['da_DK'] = $lang['en_US'];
} }
$lang['da_DK']['MultiFormSession']['db_Hash'] = 'Havelåge'; $lang['da_DK']['MultiFormSession']['db_Hash'] = 'Havelåge';
@ -24,5 +24,3 @@ $lang['da_DK']['MultiFormSession']['singular_name'] = '(ingen)';
$lang['da_DK']['MultiFormStep']['db_Data'] = 'Data'; $lang['da_DK']['MultiFormStep']['db_Data'] = 'Data';
$lang['da_DK']['MultiFormStep']['plural_name'] = '(ingen)'; $lang['da_DK']['MultiFormStep']['plural_name'] = '(ingen)';
$lang['da_DK']['MultiFormStep']['singular_name'] = '(none)'; $lang['da_DK']['MultiFormStep']['singular_name'] = '(none)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('de_DE', $lang) && is_array($lang['de_DE'])) { if (array_key_exists('de_DE', $lang) && is_array($lang['de_DE'])) {
$lang['de_DE'] = array_merge($lang['en_US'], $lang['de_DE']); $lang['de_DE'] = array_merge($lang['en_US'], $lang['de_DE']);
} else { } else {
$lang['de_DE'] = $lang['en_US']; $lang['de_DE'] = $lang['en_US'];
} }
$lang['de_DE']['MultiForm']['BACK'] = 'Zurück'; $lang['de_DE']['MultiForm']['BACK'] = 'Zurück';
@ -27,5 +27,3 @@ $lang['de_DE']['MultiFormSession']['singular_name'] = 'Multi-Formular';
$lang['de_DE']['MultiFormStep']['db_Data'] = 'Daten'; $lang['de_DE']['MultiFormStep']['db_Data'] = 'Daten';
$lang['de_DE']['MultiFormStep']['plural_name'] = 'Multi-Formular-Schritte'; $lang['de_DE']['MultiFormStep']['plural_name'] = 'Multi-Formular-Schritte';
$lang['de_DE']['MultiFormStep']['singular_name'] = 'Multi-Formular-Schritt'; $lang['de_DE']['MultiFormStep']['singular_name'] = 'Multi-Formular-Schritt';
?>

View File

@ -6,24 +6,22 @@ $lang['en_US']['MultiForm']['BACK'] = 'Back';
$lang['en_US']['MultiForm']['NEXT'] = 'Next'; $lang['en_US']['MultiForm']['NEXT'] = 'Next';
$lang['en_US']['MultiForm']['SUBMIT'] = 'Submit'; $lang['en_US']['MultiForm']['SUBMIT'] = 'Submit';
$lang['en_US']['MultiFormSession']['PLURALNAME'] = array( $lang['en_US']['MultiFormSession']['PLURALNAME'] = array(
'Multi Form Sessions', 'Multi Form Sessions',
50, 50,
'Pural name of the object, used in dropdowns and to generally identify a collection of this object in the interface' 'Pural name of the object, used in dropdowns and to generally identify a collection of this object in the interface'
); );
$lang['en_US']['MultiFormSession']['SINGULARNAME'] = array( $lang['en_US']['MultiFormSession']['SINGULARNAME'] = array(
'Multi Form Session', 'Multi Form Session',
50, 50,
'Singular name of the object, used in dropdowns and to generally identify a single object in the interface' 'Singular name of the object, used in dropdowns and to generally identify a single object in the interface'
); );
$lang['en_US']['MultiFormStep']['PLURALNAME'] = array( $lang['en_US']['MultiFormStep']['PLURALNAME'] = array(
'Multi Form Steps', 'Multi Form Steps',
50, 50,
'Pural name of the object, used in dropdowns and to generally identify a collection of this object in the interface' 'Pural name of the object, used in dropdowns and to generally identify a collection of this object in the interface'
); );
$lang['en_US']['MultiFormStep']['SINGULARNAME'] = array( $lang['en_US']['MultiFormStep']['SINGULARNAME'] = array(
'Multi Form Step', 'Multi Form Step',
50, 50,
'Singular name of the object, used in dropdowns and to generally identify a single object in the interface' 'Singular name of the object, used in dropdowns and to generally identify a single object in the interface'
); );
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('eo_XX', $lang) && is_array($lang['eo_XX'])) { if (array_key_exists('eo_XX', $lang) && is_array($lang['eo_XX'])) {
$lang['eo_XX'] = array_merge($lang['en_US'], $lang['eo_XX']); $lang['eo_XX'] = array_merge($lang['en_US'], $lang['eo_XX']);
} else { } else {
$lang['eo_XX'] = $lang['en_US']; $lang['eo_XX'] = $lang['en_US'];
} }
$lang['eo_XX']['MultiForm']['BACK'] = 'Retro'; $lang['eo_XX']['MultiForm']['BACK'] = 'Retro';
@ -27,5 +27,3 @@ $lang['eo_XX']['MultiFormSession']['singular_name'] = '(neniu)';
$lang['eo_XX']['MultiFormStep']['db_Data'] = 'Datumoj'; $lang['eo_XX']['MultiFormStep']['db_Data'] = 'Datumoj';
$lang['eo_XX']['MultiFormStep']['plural_name'] = '(neniu)'; $lang['eo_XX']['MultiFormStep']['plural_name'] = '(neniu)';
$lang['eo_XX']['MultiFormStep']['singular_name'] = '(neniu)'; $lang['eo_XX']['MultiFormStep']['singular_name'] = '(neniu)';
?>

View File

@ -10,11 +10,8 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('es_', $lang) && is_array($lang['es_'])) { if (array_key_exists('es_', $lang) && is_array($lang['es_'])) {
$lang['es_'] = array_merge($lang['en_US'], $lang['es_']); $lang['es_'] = array_merge($lang['en_US'], $lang['es_']);
} else { } else {
$lang['es_'] = $lang['en_US']; $lang['es_'] = $lang['en_US'];
} }
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('es_AR', $lang) && is_array($lang['es_AR'])) { if (array_key_exists('es_AR', $lang) && is_array($lang['es_AR'])) {
$lang['es_AR'] = array_merge($lang['en_US'], $lang['es_AR']); $lang['es_AR'] = array_merge($lang['en_US'], $lang['es_AR']);
} else { } else {
$lang['es_AR'] = $lang['en_US']; $lang['es_AR'] = $lang['en_US'];
} }
$lang['es_AR']['MultiForm']['BACK'] = 'Volver'; $lang['es_AR']['MultiForm']['BACK'] = 'Volver';
@ -27,5 +27,3 @@ $lang['es_AR']['MultiFormSession']['singular_name'] = '(ninguno)';
$lang['es_AR']['MultiFormStep']['db_Data'] = 'Datos'; $lang['es_AR']['MultiFormStep']['db_Data'] = 'Datos';
$lang['es_AR']['MultiFormStep']['plural_name'] = '(ninguno)'; $lang['es_AR']['MultiFormStep']['plural_name'] = '(ninguno)';
$lang['es_AR']['MultiFormStep']['singular_name'] = '(ninguno)'; $lang['es_AR']['MultiFormStep']['singular_name'] = '(ninguno)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('es_MX', $lang) && is_array($lang['es_MX'])) { if (array_key_exists('es_MX', $lang) && is_array($lang['es_MX'])) {
$lang['es_MX'] = array_merge($lang['en_US'], $lang['es_MX']); $lang['es_MX'] = array_merge($lang['en_US'], $lang['es_MX']);
} else { } else {
$lang['es_MX'] = $lang['en_US']; $lang['es_MX'] = $lang['en_US'];
} }
$lang['es_MX']['MultiForm']['BACK'] = 'Atrás'; $lang['es_MX']['MultiForm']['BACK'] = 'Atrás';
@ -27,5 +27,3 @@ $lang['es_MX']['MultiFormSession']['singular_name'] = '(ningún)';
$lang['es_MX']['MultiFormStep']['db_Data'] = 'Datos'; $lang['es_MX']['MultiFormStep']['db_Data'] = 'Datos';
$lang['es_MX']['MultiFormStep']['plural_name'] = '(ningunos)'; $lang['es_MX']['MultiFormStep']['plural_name'] = '(ningunos)';
$lang['es_MX']['MultiFormStep']['singular_name'] = '(ningún)'; $lang['es_MX']['MultiFormStep']['singular_name'] = '(ningún)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('et_EE', $lang) && is_array($lang['et_EE'])) { if (array_key_exists('et_EE', $lang) && is_array($lang['et_EE'])) {
$lang['et_EE'] = array_merge($lang['en_US'], $lang['et_EE']); $lang['et_EE'] = array_merge($lang['en_US'], $lang['et_EE']);
} else { } else {
$lang['et_EE'] = $lang['en_US']; $lang['et_EE'] = $lang['en_US'];
} }
$lang['et_EE']['MultiForm']['BACK'] = 'Tagasi'; $lang['et_EE']['MultiForm']['BACK'] = 'Tagasi';
@ -27,5 +27,3 @@ $lang['et_EE']['MultiFormSession']['singular_name'] = '(none)';
$lang['et_EE']['MultiFormStep']['db_Data'] = 'Andmed'; $lang['et_EE']['MultiFormStep']['db_Data'] = 'Andmed';
$lang['et_EE']['MultiFormStep']['plural_name'] = '(none)'; $lang['et_EE']['MultiFormStep']['plural_name'] = '(none)';
$lang['et_EE']['MultiFormStep']['singular_name'] = '(none)'; $lang['et_EE']['MultiFormStep']['singular_name'] = '(none)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('fr_FR', $lang) && is_array($lang['fr_FR'])) { if (array_key_exists('fr_FR', $lang) && is_array($lang['fr_FR'])) {
$lang['fr_FR'] = array_merge($lang['en_US'], $lang['fr_FR']); $lang['fr_FR'] = array_merge($lang['en_US'], $lang['fr_FR']);
} else { } else {
$lang['fr_FR'] = $lang['en_US']; $lang['fr_FR'] = $lang['en_US'];
} }
$lang['fr_FR']['MultiForm']['BACK'] = 'Retour'; $lang['fr_FR']['MultiForm']['BACK'] = 'Retour';
@ -27,5 +27,3 @@ $lang['fr_FR']['MultiFormSession']['singular_name'] = '(aucun)';
$lang['fr_FR']['MultiFormStep']['db_Data'] = 'Data'; $lang['fr_FR']['MultiFormStep']['db_Data'] = 'Data';
$lang['fr_FR']['MultiFormStep']['plural_name'] = '(aucun)'; $lang['fr_FR']['MultiFormStep']['plural_name'] = '(aucun)';
$lang['fr_FR']['MultiFormStep']['singular_name'] = '(aucun)'; $lang['fr_FR']['MultiFormStep']['singular_name'] = '(aucun)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('id_ID', $lang) && is_array($lang['id_ID'])) { if (array_key_exists('id_ID', $lang) && is_array($lang['id_ID'])) {
$lang['id_ID'] = array_merge($lang['en_US'], $lang['id_ID']); $lang['id_ID'] = array_merge($lang['en_US'], $lang['id_ID']);
} else { } else {
$lang['id_ID'] = $lang['en_US']; $lang['id_ID'] = $lang['en_US'];
} }
$lang['id_ID']['MultiForm']['BACK'] = 'Kembali'; $lang['id_ID']['MultiForm']['BACK'] = 'Kembali';
@ -26,5 +26,3 @@ $lang['id_ID']['MultiFormSession']['singular_name'] = '(tidak ada)';
$lang['id_ID']['MultiFormStep']['db_Data'] = 'Data'; $lang['id_ID']['MultiFormStep']['db_Data'] = 'Data';
$lang['id_ID']['MultiFormStep']['plural_name'] = '(tidak ada)'; $lang['id_ID']['MultiFormStep']['plural_name'] = '(tidak ada)';
$lang['id_ID']['MultiFormStep']['singular_name'] = '(tidak ada)'; $lang['id_ID']['MultiFormStep']['singular_name'] = '(tidak ada)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('is_IS', $lang) && is_array($lang['is_IS'])) { if (array_key_exists('is_IS', $lang) && is_array($lang['is_IS'])) {
$lang['is_IS'] = array_merge($lang['en_US'], $lang['is_IS']); $lang['is_IS'] = array_merge($lang['en_US'], $lang['is_IS']);
} else { } else {
$lang['is_IS'] = $lang['en_US']; $lang['is_IS'] = $lang['en_US'];
} }
$lang['is_IS']['MultiFormSession']['plural_name'] = '(ekkert)'; $lang['is_IS']['MultiFormSession']['plural_name'] = '(ekkert)';
@ -21,5 +21,3 @@ $lang['is_IS']['MultiFormSession']['singular_name'] = '(ekkert)';
$lang['is_IS']['MultiFormStep']['db_Data'] = 'Gögn'; $lang['is_IS']['MultiFormStep']['db_Data'] = 'Gögn';
$lang['is_IS']['MultiFormStep']['plural_name'] = '(ekkert)'; $lang['is_IS']['MultiFormStep']['plural_name'] = '(ekkert)';
$lang['is_IS']['MultiFormStep']['singular_name'] = '(ekkert)'; $lang['is_IS']['MultiFormStep']['singular_name'] = '(ekkert)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('it_IT', $lang) && is_array($lang['it_IT'])) { if (array_key_exists('it_IT', $lang) && is_array($lang['it_IT'])) {
$lang['it_IT'] = array_merge($lang['en_US'], $lang['it_IT']); $lang['it_IT'] = array_merge($lang['en_US'], $lang['it_IT']);
} else { } else {
$lang['it_IT'] = $lang['en_US']; $lang['it_IT'] = $lang['en_US'];
} }
$lang['it_IT']['MultiForm']['BACK'] = 'Indietro'; $lang['it_IT']['MultiForm']['BACK'] = 'Indietro';
@ -27,5 +27,3 @@ $lang['it_IT']['MultiFormSession']['singular_name'] = '(nessuno)';
$lang['it_IT']['MultiFormStep']['db_Data'] = 'Dati'; $lang['it_IT']['MultiFormStep']['db_Data'] = 'Dati';
$lang['it_IT']['MultiFormStep']['plural_name'] = '(nessuno)'; $lang['it_IT']['MultiFormStep']['plural_name'] = '(nessuno)';
$lang['it_IT']['MultiFormStep']['singular_name'] = '(nessuno)'; $lang['it_IT']['MultiFormStep']['singular_name'] = '(nessuno)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('ja_JP', $lang) && is_array($lang['ja_JP'])) { if (array_key_exists('ja_JP', $lang) && is_array($lang['ja_JP'])) {
$lang['ja_JP'] = array_merge($lang['en_US'], $lang['ja_JP']); $lang['ja_JP'] = array_merge($lang['en_US'], $lang['ja_JP']);
} else { } else {
$lang['ja_JP'] = $lang['en_US']; $lang['ja_JP'] = $lang['en_US'];
} }
$lang['ja_JP']['MultiForm']['BACK'] = '戻る'; $lang['ja_JP']['MultiForm']['BACK'] = '戻る';
@ -21,5 +21,3 @@ $lang['ja_JP']['MultiForm']['NEXT'] = '次へ';
$lang['ja_JP']['MultiForm']['SUBMIT'] = '送信'; $lang['ja_JP']['MultiForm']['SUBMIT'] = '送信';
$lang['ja_JP']['MultiFormSession']['db_Hash'] = 'ハッシュ'; $lang['ja_JP']['MultiFormSession']['db_Hash'] = 'ハッシュ';
$lang['ja_JP']['MultiFormStep']['db_Data'] = 'データ'; $lang['ja_JP']['MultiFormStep']['db_Data'] = 'データ';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('ms_MY', $lang) && is_array($lang['ms_MY'])) { if (array_key_exists('ms_MY', $lang) && is_array($lang['ms_MY'])) {
$lang['ms_MY'] = array_merge($lang['en_US'], $lang['ms_MY']); $lang['ms_MY'] = array_merge($lang['en_US'], $lang['ms_MY']);
} else { } else {
$lang['ms_MY'] = $lang['en_US']; $lang['ms_MY'] = $lang['en_US'];
} }
$lang['ms_MY']['MultiFormSession']['db_Hash'] = 'Cincangan'; $lang['ms_MY']['MultiFormSession']['db_Hash'] = 'Cincangan';
@ -24,5 +24,3 @@ $lang['ms_MY']['MultiFormSession']['singular_name'] = '(tiada)';
$lang['ms_MY']['MultiFormStep']['db_Data'] = 'Data'; $lang['ms_MY']['MultiFormStep']['db_Data'] = 'Data';
$lang['ms_MY']['MultiFormStep']['plural_name'] = '(tiada)'; $lang['ms_MY']['MultiFormStep']['plural_name'] = '(tiada)';
$lang['ms_MY']['MultiFormStep']['singular_name'] = '(tiada)'; $lang['ms_MY']['MultiFormStep']['singular_name'] = '(tiada)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('nb_NO', $lang) && is_array($lang['nb_NO'])) { if (array_key_exists('nb_NO', $lang) && is_array($lang['nb_NO'])) {
$lang['nb_NO'] = array_merge($lang['en_US'], $lang['nb_NO']); $lang['nb_NO'] = array_merge($lang['en_US'], $lang['nb_NO']);
} else { } else {
$lang['nb_NO'] = $lang['en_US']; $lang['nb_NO'] = $lang['en_US'];
} }
$lang['nb_NO']['MultiForm']['BACK'] = 'Forrige'; $lang['nb_NO']['MultiForm']['BACK'] = 'Forrige';
@ -27,5 +27,3 @@ $lang['nb_NO']['MultiFormSession']['singular_name'] = '(ingen)';
$lang['nb_NO']['MultiFormStep']['db_Data'] = 'Data'; $lang['nb_NO']['MultiFormStep']['db_Data'] = 'Data';
$lang['nb_NO']['MultiFormStep']['plural_name'] = '(ingen)'; $lang['nb_NO']['MultiFormStep']['plural_name'] = '(ingen)';
$lang['nb_NO']['MultiFormStep']['singular_name'] = '(ingen)'; $lang['nb_NO']['MultiFormStep']['singular_name'] = '(ingen)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('nl_NL', $lang) && is_array($lang['nl_NL'])) { if (array_key_exists('nl_NL', $lang) && is_array($lang['nl_NL'])) {
$lang['nl_NL'] = array_merge($lang['en_US'], $lang['nl_NL']); $lang['nl_NL'] = array_merge($lang['en_US'], $lang['nl_NL']);
} else { } else {
$lang['nl_NL'] = $lang['en_US']; $lang['nl_NL'] = $lang['en_US'];
} }
$lang['nl_NL']['MultiForm']['BACK'] = 'Terug'; $lang['nl_NL']['MultiForm']['BACK'] = 'Terug';
@ -27,5 +27,3 @@ $lang['nl_NL']['MultiFormSession']['singular_name'] = '(geen)';
$lang['nl_NL']['MultiFormStep']['db_Data'] = 'Data'; $lang['nl_NL']['MultiFormStep']['db_Data'] = 'Data';
$lang['nl_NL']['MultiFormStep']['plural_name'] = '(geen)'; $lang['nl_NL']['MultiFormStep']['plural_name'] = '(geen)';
$lang['nl_NL']['MultiFormStep']['singular_name'] = '(geen)'; $lang['nl_NL']['MultiFormStep']['singular_name'] = '(geen)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('pl_PL', $lang) && is_array($lang['pl_PL'])) { if (array_key_exists('pl_PL', $lang) && is_array($lang['pl_PL'])) {
$lang['pl_PL'] = array_merge($lang['en_US'], $lang['pl_PL']); $lang['pl_PL'] = array_merge($lang['en_US'], $lang['pl_PL']);
} else { } else {
$lang['pl_PL'] = $lang['en_US']; $lang['pl_PL'] = $lang['en_US'];
} }
$lang['pl_PL']['MultiForm']['BACK'] = 'Wstecz'; $lang['pl_PL']['MultiForm']['BACK'] = 'Wstecz';
@ -27,5 +27,3 @@ $lang['pl_PL']['MultiFormSession']['singular_name'] = '(brak)';
$lang['pl_PL']['MultiFormStep']['db_Data'] = 'Dane'; $lang['pl_PL']['MultiFormStep']['db_Data'] = 'Dane';
$lang['pl_PL']['MultiFormStep']['plural_name'] = '(brak)'; $lang['pl_PL']['MultiFormStep']['plural_name'] = '(brak)';
$lang['pl_PL']['MultiFormStep']['singular_name'] = '(brak)'; $lang['pl_PL']['MultiFormStep']['singular_name'] = '(brak)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('pt_PT', $lang) && is_array($lang['pt_PT'])) { if (array_key_exists('pt_PT', $lang) && is_array($lang['pt_PT'])) {
$lang['pt_PT'] = array_merge($lang['en_US'], $lang['pt_PT']); $lang['pt_PT'] = array_merge($lang['en_US'], $lang['pt_PT']);
} else { } else {
$lang['pt_PT'] = $lang['en_US']; $lang['pt_PT'] = $lang['en_US'];
} }
$lang['pt_PT']['MultiFormSession']['db_IsComplete'] = 'Está completa'; $lang['pt_PT']['MultiFormSession']['db_IsComplete'] = 'Está completa';
@ -23,5 +23,3 @@ $lang['pt_PT']['MultiFormSession']['singular_name'] = '(nenhum)';
$lang['pt_PT']['MultiFormStep']['db_Data'] = 'Dados'; $lang['pt_PT']['MultiFormStep']['db_Data'] = 'Dados';
$lang['pt_PT']['MultiFormStep']['plural_name'] = '(nenhum)'; $lang['pt_PT']['MultiFormStep']['plural_name'] = '(nenhum)';
$lang['pt_PT']['MultiFormStep']['singular_name'] = '(nenhum)'; $lang['pt_PT']['MultiFormStep']['singular_name'] = '(nenhum)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('sr_RS', $lang) && is_array($lang['sr_RS'])) { if (array_key_exists('sr_RS', $lang) && is_array($lang['sr_RS'])) {
$lang['sr_RS'] = array_merge($lang['en_US'], $lang['sr_RS']); $lang['sr_RS'] = array_merge($lang['en_US'], $lang['sr_RS']);
} else { } else {
$lang['sr_RS'] = $lang['en_US']; $lang['sr_RS'] = $lang['en_US'];
} }
$lang['sr_RS']['MultiFormSession']['db_IsComplete'] = 'ЈеЗавршен'; $lang['sr_RS']['MultiFormSession']['db_IsComplete'] = 'ЈеЗавршен';
@ -22,5 +22,3 @@ $lang['sr_RS']['MultiFormSession']['singular_name'] = '(без)';
$lang['sr_RS']['MultiFormStep']['db_Data'] = 'Подаци'; $lang['sr_RS']['MultiFormStep']['db_Data'] = 'Подаци';
$lang['sr_RS']['MultiFormStep']['plural_name'] = '(без)'; $lang['sr_RS']['MultiFormStep']['plural_name'] = '(без)';
$lang['sr_RS']['MultiFormStep']['singular_name'] = '(без)'; $lang['sr_RS']['MultiFormStep']['singular_name'] = '(без)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('sv_SE', $lang) && is_array($lang['sv_SE'])) { if (array_key_exists('sv_SE', $lang) && is_array($lang['sv_SE'])) {
$lang['sv_SE'] = array_merge($lang['en_US'], $lang['sv_SE']); $lang['sv_SE'] = array_merge($lang['en_US'], $lang['sv_SE']);
} else { } else {
$lang['sv_SE'] = $lang['en_US']; $lang['sv_SE'] = $lang['en_US'];
} }
$lang['sv_SE']['MultiForm']['BACK'] = 'Tillbaka'; $lang['sv_SE']['MultiForm']['BACK'] = 'Tillbaka';
@ -24,5 +24,3 @@ $lang['sv_SE']['MultiFormSession']['plural_name'] = '(ingen)';
$lang['sv_SE']['MultiFormStep']['db_Data'] = 'Data'; $lang['sv_SE']['MultiFormStep']['db_Data'] = 'Data';
$lang['sv_SE']['MultiFormStep']['plural_name'] = '(inga)'; $lang['sv_SE']['MultiFormStep']['plural_name'] = '(inga)';
$lang['sv_SE']['MultiFormStep']['singular_name'] = '(ingen)'; $lang['sv_SE']['MultiFormStep']['singular_name'] = '(ingen)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('tr_TR', $lang) && is_array($lang['tr_TR'])) { if (array_key_exists('tr_TR', $lang) && is_array($lang['tr_TR'])) {
$lang['tr_TR'] = array_merge($lang['en_US'], $lang['tr_TR']); $lang['tr_TR'] = array_merge($lang['en_US'], $lang['tr_TR']);
} else { } else {
$lang['tr_TR'] = $lang['en_US']; $lang['tr_TR'] = $lang['en_US'];
} }
$lang['tr_TR']['MultiForm']['BACK'] = 'Geri'; $lang['tr_TR']['MultiForm']['BACK'] = 'Geri';
@ -27,5 +27,3 @@ $lang['tr_TR']['MultiFormSession']['singular_name'] = '(hiçbiri)';
$lang['tr_TR']['MultiFormStep']['db_Data'] = 'Veri'; $lang['tr_TR']['MultiFormStep']['db_Data'] = 'Veri';
$lang['tr_TR']['MultiFormStep']['plural_name'] = '(hiçbiri)'; $lang['tr_TR']['MultiFormStep']['plural_name'] = '(hiçbiri)';
$lang['tr_TR']['MultiFormStep']['singular_name'] = '(hiçbiri)'; $lang['tr_TR']['MultiFormStep']['singular_name'] = '(hiçbiri)';
?>

View File

@ -10,10 +10,10 @@ i18n::include_locale_file('modules: multiform', 'en_US');
global $lang; global $lang;
if(array_key_exists('zh_CN', $lang) && is_array($lang['zh_CN'])) { if (array_key_exists('zh_CN', $lang) && is_array($lang['zh_CN'])) {
$lang['zh_CN'] = array_merge($lang['en_US'], $lang['zh_CN']); $lang['zh_CN'] = array_merge($lang['en_US'], $lang['zh_CN']);
} else { } else {
$lang['zh_CN'] = $lang['en_US']; $lang['zh_CN'] = $lang['en_US'];
} }
$lang['zh_CN']['MultiForm']['BACK'] = '区域'; $lang['zh_CN']['MultiForm']['BACK'] = '区域';
@ -27,5 +27,3 @@ $lang['zh_CN']['MultiFormSession']['singular_name'] = '单名称';
$lang['zh_CN']['MultiFormStep']['db_Data'] = '数据'; $lang['zh_CN']['MultiFormStep']['db_Data'] = '数据';
$lang['zh_CN']['MultiFormStep']['plural_name'] = '多名称'; $lang['zh_CN']['MultiFormStep']['plural_name'] = '多名称';
$lang['zh_CN']['MultiFormStep']['singular_name'] = '单名称'; $lang['zh_CN']['MultiFormStep']['singular_name'] = '单名称';
?>

View File

@ -14,56 +14,58 @@
* *
* @package multiform * @package multiform
*/ */
class MultiFormObjectDecorator extends DataExtension { class MultiFormObjectDecorator extends DataExtension
{
private static $db = array( private static $db = array(
'MultiFormIsTemporary' => 'Boolean', 'MultiFormIsTemporary' => 'Boolean',
); );
private static $has_one = array( private static $has_one = array(
'MultiFormSession' => 'MultiFormSession', 'MultiFormSession' => 'MultiFormSession',
); );
/** /**
* Augment any queries to MultiFormObjectDecorator and only * Augment any queries to MultiFormObjectDecorator and only
* return anything that isn't considered temporary. * return anything that isn't considered temporary.
*/ */
public function augmentSQL(SQLQuery &$query) { public function augmentSQL(SQLQuery &$query)
$where = $query->getWhere(); {
if(!$where && !$this->wantsTemporary($query)) { $where = $query->getWhere();
$from = array_values($query->getFrom()); if (!$where && !$this->wantsTemporary($query)) {
$query->addWhere("{$from[0]}.\"MultiFormIsTemporary\" = '0'"); $from = array_values($query->getFrom());
return; $query->addWhere("{$from[0]}.\"MultiFormIsTemporary\" = '0'");
} return;
}
if( if (strpos($where[0], ".`ID` = ") === false
strpos($where[0], ".`ID` = ") === false && strpos($where[0], ".ID = ") === false
&& strpos($where[0], ".ID = ") === false && strpos($where[0], "ID = ") !== 0
&& strpos($where[0], "ID = ") !== 0 && !$this->wantsTemporary($query)
&& !$this->wantsTemporary($query) ) {
) { $from = array_values($query->getFrom());
$from = array_values($query->getFrom()); $query->addWhere("{$from[0]}.\"MultiFormIsTemporary\" = '0'");
$query->addWhere("{$from[0]}.\"MultiFormIsTemporary\" = '0'"); }
} }
}
/** /**
* Determines if the current query is supposed * Determines if the current query is supposed
* to be exempt from the automatic filtering out * to be exempt from the automatic filtering out
* of temporary records. * of temporary records.
* *
* @param SQLQuery $query * @param SQLQuery $query
* @return boolean * @return boolean
*/ */
protected function wantsTemporary($query) { protected function wantsTemporary($query)
foreach($query->getWhere() as $whereClause) { {
$from = array_values($query->getFrom()); foreach ($query->getWhere() as $whereClause) {
// SQLQuery will automatically add double quotes and single quotes to values, so check against that. $from = array_values($query->getFrom());
if($whereClause == "{$from[0]}.\"MultiFormIsTemporary\" = '1'") { // SQLQuery will automatically add double quotes and single quotes to values, so check against that.
return true; if ($whereClause == "{$from[0]}.\"MultiFormIsTemporary\" = '1'") {
} return true;
} }
}
return false; return false;
} }
} }

View File

@ -14,686 +14,721 @@
* *
* @package multiform * @package multiform
*/ */
abstract class MultiForm extends Form { abstract class MultiForm extends Form
{
/**
* A session object stored in the database, to identify and store /**
* data for this MultiForm instance. * A session object stored in the database, to identify and store
* * data for this MultiForm instance.
* @var MultiFormSession *
*/ * @var MultiFormSession
protected $session; */
protected $session;
/**
* The current encrypted MultiFormSession identification. /**
* * The current encrypted MultiFormSession identification.
* @var string *
*/ * @var string
protected $currentSessionHash; */
protected $currentSessionHash;
/**
* Defines which subclass of {@link MultiFormStep} should be the first /**
* step in the multi-step process. * Defines which subclass of {@link MultiFormStep} should be the first
* * step in the multi-step process.
* @var string Classname of a {@link MultiFormStep} subclass *
*/ * @var string Classname of a {@link MultiFormStep} subclass
public static $start_step; */
public static $start_step;
/**
* Set the casting for these fields. /**
* * Set the casting for these fields.
* @var array *
*/ * @var array
private static $casting = array( */
'CompletedStepCount' => 'Int', private static $casting = array(
'TotalStepCount' => 'Int', 'CompletedStepCount' => 'Int',
'CompletedPercent' => 'Float' 'TotalStepCount' => 'Int',
); 'CompletedPercent' => 'Float'
);
/**
* @var string /**
*/ * @var string
private static $get_var = 'MultiFormSessionID'; */
private static $get_var = 'MultiFormSessionID';
/**
* 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 * These fields are ignored when saving the raw form data into session.
* or potentially dangerous. * This ensures only field data is saved, and nothing else that's useless
* * or potentially dangerous.
* @var array *
*/ * @var array
public static $ignored_fields = array( */
'url', public static $ignored_fields = array(
'executeForm', 'url',
'SecurityID' 'executeForm',
); 'SecurityID'
);
/**
* Any of the actions defined in this variable are exempt from /**
* being validated. * Any of the actions defined in this variable are exempt from
* * being validated.
* This is most useful for the "Back" (action_prev) action, as *
* you typically don't validate the form when the user is going * This is most useful for the "Back" (action_prev) action, as
* back a step. * you typically don't validate the form when the user is going
* * back a step.
* @var array *
*/ * @var array
public static $actions_exempt_from_validation = array( */
'action_prev' public static $actions_exempt_from_validation = array(
); 'action_prev'
);
/**
* @var string /**
*/ * @var string
protected $displayLink; */
protected $displayLink;
/**
* Flag which is being used in getAllStepsRecursive() to allow adding the completed flag on the steps /**
* * Flag which is being used in getAllStepsRecursive() to allow adding the completed flag on the steps
* @var boolean *
*/ * @var boolean
protected $currentStepHasBeenFound = false; */
protected $currentStepHasBeenFound = false;
/**
* Start the MultiForm instance. /**
* * 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 * @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) { */
// First set the controller and name manually so they are available for public function __construct($controller, $name)
// field construction. {
$this->controller = $controller; // First set the controller and name manually so they are available for
$this->name = $name; // field construction.
$this->controller = $controller;
// Set up the session for this MultiForm instance $this->name = $name;
$this->setSession();
// Set up the session for this MultiForm instance
// Get the current step available (Note: either returns an existing $this->setSession();
// step or creates a new one if none available)
$currentStep = $this->getCurrentStep(); // Get the current step available (Note: either returns an existing
// step or creates a new one if none available)
// Set the step returned above as the current step $currentStep = $this->getCurrentStep();
$this->setCurrentStep($currentStep);
// Set the step returned above as the current step
// Set the form of the step to this form instance $this->setCurrentStep($currentStep);
$currentStep->setForm($this);
// Set the form of the step to this form instance
// Set up the fields for the current step $currentStep->setForm($this);
$fields = $currentStep->getFields();
// Set up the fields for the current step
// Set up the actions for the current step $fields = $currentStep->getFields();
$actions = $this->actionsFor($currentStep);
// Set up the actions for the current step
// Set up validation (if necessary) $actions = $this->actionsFor($currentStep);
$validator = null;
$applyValidation = true; // Set up validation (if necessary)
$validator = null;
$actionNames = static::$actions_exempt_from_validation; $applyValidation = true;
if($actionNames) { $actionNames = static::$actions_exempt_from_validation;
foreach ($actionNames as $exemptAction) {
if(!empty($_REQUEST[$exemptAction])) { if ($actionNames) {
$applyValidation = false; foreach ($actionNames as $exemptAction) {
break; if (!empty($_REQUEST[$exemptAction])) {
} $applyValidation = false;
} break;
} }
}
// Apply validation if the current step requires validation (is not exempt) }
if($applyValidation) {
if($currentStep->getValidator()) { // Apply validation if the current step requires validation (is not exempt)
$validator = $currentStep->getValidator(); if ($applyValidation) {
} if ($currentStep->getValidator()) {
} $validator = $currentStep->getValidator();
}
// Give the fields, actions, and validation for the current step back to the parent Form class }
parent::__construct($controller, $name, $fields, $actions, $validator);
// Give the fields, actions, and validation for the current step back to the parent Form class
$getVar = $this->config()->get_var; parent::__construct($controller, $name, $fields, $actions, $validator);
// Set a hidden field in our form with an encrypted hash to identify this session. $getVar = $this->config()->get_var;
$this->fields->push(new HiddenField($getVar, false, $this->session->Hash));
// Set a hidden field in our form with an encrypted hash to identify this session.
// If there is saved data for the current step, we load it into the form it here $this->fields->push(new HiddenField($getVar, false, $this->session->Hash));
//(CAUTION: loadData() MUST unserialize first!)
if($data = $currentStep->loadData()) { // If there is saved data for the current step, we load it into the form it here
$this->loadDataFrom($data); //(CAUTION: loadData() MUST unserialize first!)
} if ($data = $currentStep->loadData()) {
$this->loadDataFrom($data);
// Disable security token - we tie a form to a session ID instead }
$this->disableSecurityToken();
// Disable security token - we tie a form to a session ID instead
self::$ignored_fields[] = $getVar; $this->disableSecurityToken();
}
self::$ignored_fields[] = $getVar;
/** }
* Accessor method to $this->controller.
* /**
* @return Controller this MultiForm was instanciated on. * Accessor method to $this->controller.
*/ *
public function getController() { * @return Controller this MultiForm was instanciated on.
return $this->controller; */
} public function getController()
{
/** return $this->controller;
* Returns the get_var to the template engine }
*
* @return string /**
*/ * Returns the get_var to the template engine
public function getGetVar() { *
return $this->config()->get_var; * @return string
} */
public function getGetVar()
/** {
* Get the current step. return $this->config()->get_var;
* }
* 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 * Get the current step.
* just been started, and so we create the first step and return it. *
* * If StepID has been set in the URL, we attempt to get that record
* @return MultiFormStep subclass * 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
public function getCurrentStep() { * just been started, and so we create the first step and return it.
$startStepClass = static::$start_step; *
* @return MultiFormStep subclass
// Check if there was a start step defined on the subclass of MultiForm */
if(!isset($startStepClass)) user_error( public function getCurrentStep()
'MultiForm::init(): Please define a $start_step on ' . $this->class, {
E_USER_ERROR $startStepClass = static::$start_step;
);
// Check if there was a start step defined on the subclass of MultiForm
// Determine whether we use the current step, or create one if it doesn't exist if (!isset($startStepClass)) {
$currentStep = null; user_error(
$StepID = $this->controller->request->getVar('StepID'); 'MultiForm::init(): Please define a $start_step on ' . $this->class,
if(isset($StepID)) { E_USER_ERROR
$currentStep = DataObject::get_one( );
'MultiFormStep', }
array(
'SessionID' => $this->session->ID, // Determine whether we use the current step, or create one if it doesn't exist
'ID' => $StepID $currentStep = null;
) $StepID = $this->controller->request->getVar('StepID');
); if (isset($StepID)) {
} elseif($this->session->CurrentStepID) { $currentStep = DataObject::get_one(
$currentStep = $this->session->CurrentStep(); 'MultiFormStep',
} array(
'SessionID' => $this->session->ID,
// Always fall back to creating a new step (in case the session or request data is invalid) 'ID' => $StepID
if(!$currentStep || !$currentStep->ID) { )
$currentStep = Object::create($startStepClass); );
$currentStep->SessionID = $this->session->ID; } elseif ($this->session->CurrentStepID) {
$currentStep->write(); $currentStep = $this->session->CurrentStep();
$this->session->CurrentStepID = $currentStep->ID; }
$this->session->write();
$this->session->flushCache(); // Always fall back to creating a new step (in case the session or request data is invalid)
} if (!$currentStep || !$currentStep->ID) {
$currentStep = Object::create($startStepClass);
if($currentStep) $currentStep->setForm($this); $currentStep->SessionID = $this->session->ID;
$currentStep->write();
return $currentStep; $this->session->CurrentStepID = $currentStep->ID;
} $this->session->write();
$this->session->flushCache();
/** }
* Set the step passed in as the current step.
* if ($currentStep) {
* @param MultiFormStep $step A subclass of MultiFormStep $currentStep->setForm($this);
* @return boolean The return value of write() }
*/
protected function setCurrentStep($step) { return $currentStep;
$this->session->CurrentStepID = $step->ID; }
$step->setForm($this);
/**
return $this->session->write(); * Set the step passed in as the current step.
} *
* @param MultiFormStep $step A subclass of MultiFormStep
/** * @return boolean The return value of write()
* Accessor method to $this->session. */
* protected function setCurrentStep($step)
* @return MultiFormSession {
*/ $this->session->CurrentStepID = $step->ID;
public function getSession() { $step->setForm($this);
return $this->session;
} return $this->session->write();
}
/**
* Set up the session. /**
* * Accessor method to $this->session.
* If MultiFormSessionID isn't set, we assume that this is a new *
* multiform that requires a new session record to be created. * @return MultiFormSession
* */
* @TODO Fix the fact you can continually refresh and create new records public function getSession()
* if MultiFormSessionID isn't set. {
* return $this->session;
* @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() { * Set up the session.
$this->session = $this->getCurrentSession(); *
* If MultiFormSessionID isn't set, we assume that this is a new
// If there was no session found, create a new one instead * multiform that requires a new session record to be created.
if(!$this->session) { *
$this->session = new MultiFormSession(); * @TODO Fix the fact you can continually refresh and create new records
} * if MultiFormSessionID isn't set.
*
// Create encrypted identification to the session instance if it doesn't exist * @TODO Not sure if we should bake the session stuff directly into MultiForm.
if(!$this->session->Hash) { * Perhaps it would be best dealt with on a separate class?
$this->session->Hash = sha1($this->session->ID . '-' . microtime()); */
$this->session->write(); protected function setSession()
} {
} $this->session = $this->getCurrentSession();
/** // If there was no session found, create a new one instead
* Set the currently used encrypted hash to identify if (!$this->session) {
* the MultiFormSession. $this->session = new MultiFormSession();
* }
* @param string $hash Encrypted identification to session
*/ // Create encrypted identification to the session instance if it doesn't exist
public function setCurrentSessionHash($hash) { if (!$this->session->Hash) {
$this->currentSessionHash = $hash; $this->session->Hash = sha1($this->session->ID . '-' . microtime());
$this->setSession(); $this->session->write();
} }
}
/**
* Return the currently used {@link MultiFormSession} /**
* @return MultiFormSession|boolean FALSE * Set the currently used encrypted hash to identify
*/ * the MultiFormSession.
public function getCurrentSession() { *
if(!$this->currentSessionHash) { * @param string $hash Encrypted identification to session
$this->currentSessionHash = $this->controller->request->getVar($this->config()->get_var); */
public function setCurrentSessionHash($hash)
if(!$this->currentSessionHash) { {
return false; $this->currentSessionHash = $hash;
} $this->setSession();
} }
$this->session = MultiFormSession::get()->filter(array( /**
"Hash" => $this->currentSessionHash, * Return the currently used {@link MultiFormSession}
"IsComplete" => 0 * @return MultiFormSession|boolean FALSE
))->first(); */
public function getCurrentSession()
return $this->session; {
} if (!$this->currentSessionHash) {
$this->currentSessionHash = $this->controller->request->getVar($this->config()->get_var);
/**
* Get all steps saved in the database for the currently active session, if (!$this->currentSessionHash) {
* in the order they were saved, oldest to newest (automatically ordered by ID). return false;
* If you want a full chain of steps regardless if they've already been saved }
* to the database, use {@link getAllStepsLinear()}. }
*
* @param string $filter SQL WHERE statement $this->session = MultiFormSession::get()->filter(array(
* @return DataObjectSet|boolean A set of MultiFormStep subclasses "Hash" => $this->currentSessionHash,
*/ "IsComplete" => 0
public function getSavedSteps($filter = null) { ))->first();
$filter .= ($filter) ? ' AND ' : '';
$filter .= sprintf("\"SessionID\" = '%s'", $this->session->ID); return $this->session;
return DataObject::get('MultiFormStep', $filter); }
}
/**
/** * Get all steps saved in the database for the currently active session,
* Get a step which was previously saved to the database in the current session. * in the order they were saved, oldest to newest (automatically ordered by ID).
* Caution: This might cause unexpected behaviour if you have multiple steps * If you want a full chain of steps regardless if they've already been saved
* in your chain with the same classname. * to the database, use {@link getAllStepsLinear()}.
* *
* @param string $className Classname of a {@link MultiFormStep} subclass * @param string $filter SQL WHERE statement
* @return MultiFormStep * @return DataObjectSet|boolean A set of MultiFormStep subclasses
*/ */
public function getSavedStepByClass($className) { public function getSavedSteps($filter = null)
return DataObject::get_one( {
'MultiFormStep', $filter .= ($filter) ? ' AND ' : '';
sprintf("\"SessionID\" = '%s' AND \"ClassName\" = '%s'", $filter .= sprintf("\"SessionID\" = '%s'", $this->session->ID);
$this->session->ID, return DataObject::get('MultiFormStep', $filter);
Convert::raw2sql($className) }
)
); /**
} * 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.
* Build a FieldList of the FormAction fields for the given step. *
* * @param string $className Classname of a {@link MultiFormStep} subclass
* If the current step is the final step, we push in a submit button, which * @return MultiFormStep
* calls the action {@link finish()} to finalise the submission. Otherwise, */
* we push in a next button which calls the action {@link next()} to determine public function getSavedStepByClass($className)
* where to go next in our step process, and save any form data collected. {
* return DataObject::get_one(
* If there's a previous step (a step that has the current step as it's next 'MultiFormStep',
* step class), then we allow a previous button, which calls the previous action sprintf(
* to determine which step to go back to. "\"SessionID\" = '%s' AND \"ClassName\" = '%s'",
* $this->session->ID,
* If there are any extra actions defined in MultiFormStep->getExtraActions() Convert::raw2sql($className)
* 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 FieldList of FormAction objects /**
*/ * Build a FieldList of the FormAction fields for the given step.
public function actionsFor($step) { *
// Create default multi step actions (next, prev), and merge with extra actions, if any * If the current step is the final step, we push in a submit button, which
$actions = (class_exists('FieldList')) ? new FieldList() : new FieldSet(); * calls the action {@link finish()} to finalise the submission. Otherwise,
* we push in a next button which calls the action {@link next()} to determine
// If the form is at final step, create a submit button to perform final actions * where to go next in our step process, and save any form data collected.
// 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()) { * If there's a previous step (a step that has the current step as it's next
$actions->push(new FormAction('finish', $step->getSubmitText())); * step class), then we allow a previous button, which calls the previous action
} else { * to determine which step to go back to.
$actions->push(new FormAction('next', $step->getNextText())); *
} * 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
// If there is a previous step defined, add the back button * have created in this method.
if($step->getPreviousStep() && $step->canGoBack()) { *
// If there is a next step, insert the action before the next action * @param $currentStep Subclass of MultiFormStep
if($step->getNextStep()) { * @return FieldList of FormAction objects
$actions->insertBefore($prev = new FormAction('prev', $step->getPrevText()), 'action_next'); */
// Assume that this is the last step, insert the action before the finish action public function actionsFor($step)
} else { {
$actions->insertBefore($prev = new FormAction('prev', $step->getPrevText()), 'action_finish'); // Create default multi step actions (next, prev), and merge with extra actions, if any
} $actions = (class_exists('FieldList')) ? new FieldList() : new FieldSet();
//remove browser validation from prev action
$prev->setAttribute("formnovalidate", "formnovalidate"); // 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()) {
// Merge any extra action fields defined on the step $actions->push(new FormAction('finish', $step->getSubmitText()));
$actions->merge($step->getExtraActions()); } else {
$actions->push(new FormAction('next', $step->getNextText()));
return $actions; }
}
// If there is a previous step defined, add the back button
/** if ($step->getPreviousStep() && $step->canGoBack()) {
* Return a rendered version of this form, with a specific template. // If there is a next step, insert the action before the next action
* Looks through the step ancestory templates (MultiFormStep, current step if ($step->getNextStep()) {
* subclass template) to see if one is available to render the form with. If $actions->insertBefore($prev = new FormAction('prev', $step->getPrevText()), 'action_next');
* any of those don't exist, look for a default Form template to render // Assume that this is the last step, insert the action before the finish action
* with instead. } else {
* $actions->insertBefore($prev = new FormAction('prev', $step->getPrevText()), 'action_finish');
* @return SSViewer object to render the template with }
*/ //remove browser validation from prev action
public function forTemplate() { $prev->setAttribute("formnovalidate", "formnovalidate");
$return = $this->renderWith(array( }
$this->getCurrentStep()->class,
'MultiFormStep', // Merge any extra action fields defined on the step
$this->class, $actions->merge($step->getExtraActions());
'MultiForm',
'Form' return $actions;
)); }
$this->clearMessage(); /**
* Return a rendered version of this form, with a specific template.
return $return; * 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.
* This method saves the data on the final step, after submitting. *
* It should always be overloaded with parent::finish($data, $form) * @return SSViewer object to render the template with
* so you can create your own functionality which handles saving */
* of all the data collected through each step of the form. public function forTemplate()
* {
* @param array $data The request data returned from the form $return = $this->renderWith(array(
* @param object $form The form that the action was called on $this->getCurrentStep()->class,
*/ 'MultiFormStep',
public function finish($data, $form) { $this->class,
// Save the form data for the current step 'MultiForm',
$this->save($data); 'Form'
));
if(!$this->getCurrentStep()->isFinalStep()) {
$this->controller->redirectBack(); $this->clearMessage();
return false;
} return $return;
}
if(!$this->getCurrentStep()->validateStep($data, $form)) {
Session::set("FormInfo.{$form->FormName()}.data", $form->getData()); /**
$this->controller->redirectBack(); * This method saves the data on the final step, after submitting.
return false; * 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
* Determine what to do when the next action is called. * @param object $form The form that the action was called on
* */
* Saves the current step session data to the database, creates the public function finish($data, $form)
* new step based on getNextStep() of the current step (or fetches {
* an existing one), resets the current step to the next step, // Save the form data for the current step
* then redirects to the newly set step. $this->save($data);
*
* @param array $data The request data returned from the form if (!$this->getCurrentStep()->isFinalStep()) {
* @param object $form The form that the action was called on $this->controller->redirectBack();
*/ return false;
public function next($data, $form) { }
// Save the form data for the current step
$this->save($form->getData()); if (!$this->getCurrentStep()->validateStep($data, $form)) {
Session::set("FormInfo.{$form->FormName()}.data", $form->getData());
// Get the next step class $this->controller->redirectBack();
$nextStepClass = $this->getCurrentStep()->getNextStep(); return false;
}
if(!$nextStepClass) { }
$this->controller->redirectBack();
return false; /**
} * Determine what to do when the next action is called.
*
// Perform custom step validation (use MultiFormStep->getValidator() for * Saves the current step session data to the database, creates the
// built-in functionality). The data needs to be manually saved on error * new step based on getNextStep() of the current step (or fetches
// so the form is re-populated. * an existing one), resets the current step to the next step,
if(!$this->getCurrentStep()->validateStep($data, $form)) { * then redirects to the newly set step.
Session::set("FormInfo.{$form->FormName()}.data", $form->getData()); *
$this->controller->redirectBack(); * @param array $data The request data returned from the form
return false; * @param object $form The form that the action was called on
} */
public function next($data, $form)
// validation succeeded so we reset it to remove errors and messages {
$this->resetValidation(); // 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}")) { // Get the next step class
$nextStep = Object::create($nextStepClass); $nextStepClass = $this->getCurrentStep()->getNextStep();
$nextStep->SessionID = $this->session->ID;
$nextStep->write(); if (!$nextStepClass) {
} $this->controller->redirectBack();
return false;
// Set the next step found as the current step }
$this->setCurrentStep($nextStep);
// Perform custom step validation (use MultiFormStep->getValidator() for
// Redirect to the next step // built-in functionality). The data needs to be manually saved on error
$this->controller->redirect($nextStep->Link()); // so the form is re-populated.
} if (!$this->getCurrentStep()->validateStep($data, $form)) {
Session::set("FormInfo.{$form->FormName()}.data", $form->getData());
/** $this->controller->redirectBack();
* Determine what to do when the previous action is called. return false;
* }
* Retrieves the previous step class, finds the record for that
* class in the DB, and sets the current step to that step found. // validation succeeded so we reset it to remove errors and messages
* Finally, it redirects to that step. $this->resetValidation();
*
* @param array $data The request data returned from the form // Determine whether we can use a step already in the DB, or have to create a new one
* @param object $form The form that the action was called on if (!$nextStep = DataObject::get_one($nextStepClass, "\"SessionID\" = {$this->session->ID}")) {
*/ $nextStep = Object::create($nextStepClass);
public function prev($data, $form) { $nextStep->SessionID = $this->session->ID;
// Save the form data for the current step $nextStep->write();
$this->save($form->getData()); }
// Get the previous step class // Set the next step found as the current step
$prevStepClass = $this->getCurrentStep()->getPreviousStep(); $this->setCurrentStep($nextStep);
if(!$prevStepClass && !$this->getCurrentStep()->canGoBack()) { // Redirect to the next step
$this->controller->redirectBack(); $this->controller->redirect($nextStep->Link());
return false; }
}
/**
// Get the previous step of the class instance returned from $currentStep->getPreviousStep() * Determine what to do when the previous action is called.
$prevStep = DataObject::get_one($prevStepClass, "\"SessionID\" = {$this->session->ID}"); *
* Retrieves the previous step class, finds the record for that
// Set the current step as the previous step * class in the DB, and sets the current step to that step found.
$this->setCurrentStep($prevStep); * Finally, it redirects to that step.
*
// Redirect to the previous step * @param array $data The request data returned from the form
$this->controller->redirect($prevStep->Link()); * @param object $form The form that the action was called on
} */
public function prev($data, $form)
/** {
* Save the raw data given back from the form into session. // Save the form data for the current step
* $this->save($form->getData());
* Take the submitted form data for the current step, removing
* any key => value pairs that shouldn't be saved, then saves // Get the previous step class
* the data into the session. $prevStepClass = $this->getCurrentStep()->getPreviousStep();
*
* @param array $data An array of data to save if (!$prevStepClass && !$this->getCurrentStep()->canGoBack()) {
*/ $this->controller->redirectBack();
protected function save($data) { return false;
$currentStep = $this->getCurrentStep(); }
if(is_array($data)) {
foreach($data as $field => $value) { // Get the previous step of the class instance returned from $currentStep->getPreviousStep()
if(in_array($field, static::$ignored_fields)) { $prevStep = DataObject::get_one($prevStepClass, "\"SessionID\" = {$this->session->ID}");
unset($data[$field]);
} // Set the current step as the previous step
} $this->setCurrentStep($prevStep);
$currentStep->saveData($data);
} // Redirect to the previous step
return; $this->controller->redirect($prevStep->Link());
} }
// ############ Misc ############ /**
* Save the raw data given back from the form into session.
/** *
* Add the MultiFormSessionID variable to the URL on form submission. * Take the submitted form data for the current step, removing
* This is a means to persist the session, by adding it's identification * any key => value pairs that shouldn't be saved, then saves
* to the URL, which ties it back to this MultiForm instance. * the data into the session.
* *
* @return string * @param array $data An array of data to save
*/ */
public function FormAction() { protected function save($data)
$action = parent::FormAction(); {
$action .= (strpos($action, '?')) ? '&amp;' : '?'; $currentStep = $this->getCurrentStep();
$action .= "{$this->config()->get_var}={$this->session->Hash}"; if (is_array($data)) {
foreach ($data as $field => $value) {
return $action; if (in_array($field, static::$ignored_fields)) {
} unset($data[$field]);
}
/** }
* Returns the link to the page where the form is displayed. The user is $currentStep->saveData($data);
* redirected to this link with a session param after each step is }
* submitted. return;
* }
* @return string
*/ // ############ Misc ############
public function getDisplayLink() {
return $this->displayLink ? $this->displayLink : Controller::curr()->Link(); /**
} * 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.
* Set the link to the page on which the form is displayed. *
* * @return string
* The link defaults to the controllers current link. However if the form */
* is displayed inside an action the display link must be explicitly set. public function FormAction()
* {
* @param string $link $action = parent::FormAction();
*/ $action .= (strpos($action, '?')) ? '&amp;' : '?';
public function setDisplayLink($link) { $action .= "{$this->config()->get_var}={$this->session->Hash}";
$this->displayLink = $link;
} return $action;
}
/**
* Determine the steps to show in a linear fashion, starting from the /**
* first step. We run {@link getAllStepsRecursive} passing the steps found * Returns the link to the page where the form is displayed. The user is
* by reference to get a listing of the steps. * redirected to this link with a session param after each step is
* * submitted.
* @return DataObjectSet of MultiFormStep instances *
*/ * @return string
public function getAllStepsLinear() { */
$stepsFound = (class_exists('ArrayList')) ? new ArrayList() : new DataObjectSet(); public function getDisplayLink()
{
$firstStep = DataObject::get_one(static::$start_step, "\"SessionID\" = {$this->session->ID}"); return $this->displayLink ? $this->displayLink : Controller::curr()->Link();
$firstStep->LinkingMode = ($firstStep->ID == $this->getCurrentStep()->ID) ? 'current' : 'link'; }
$firstStep->setForm($this);
$stepsFound->push($firstStep); /**
* Set the link to the page on which the form is displayed.
// mark the further steps as non-completed if the first step is the current *
if ($firstStep->ID == $this->getCurrentStep()->ID) { * The link defaults to the controllers current link. However if the form
$this->currentStepHasBeenFound = true; * is displayed inside an action the display link must be explicitly set.
} else { *
$firstStep->addExtraClass('completed'); * @param string $link
} */
public function setDisplayLink($link)
$this->getAllStepsRecursive($firstStep, $stepsFound); {
$this->displayLink = $link;
return $stepsFound; }
}
/**
/** * Determine the steps to show in a linear fashion, starting from the
* Recursively run through steps using the getNextStep() method on each step * first step. We run {@link getAllStepsRecursive} passing the steps found
* to determine what the next step is, gathering each step along the way. * by reference to get a listing of the steps.
* 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 * @return DataObjectSet of MultiFormStep instances
* 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. public function getAllStepsLinear()
* {
* @param $step Subclass of MultiFormStep to find the next step of $stepsFound = (class_exists('ArrayList')) ? new ArrayList() : new DataObjectSet();
* @param $stepsFound $stepsFound DataObjectSet reference, the steps found to call back on
* @return DataObjectSet of MultiFormStep instances $firstStep = DataObject::get_one(static::$start_step, "\"SessionID\" = {$this->session->ID}");
*/ $firstStep->LinkingMode = ($firstStep->ID == $this->getCurrentStep()->ID) ? 'current' : 'link';
protected function getAllStepsRecursive($step, &$stepsFound) { $firstStep->setForm($this);
// Find the next step to the current step, the final step has no next step $stepsFound->push($firstStep);
if(!$step->isFinalStep()) {
if($step->getNextStep()) { // mark the further steps as non-completed if the first step is the current
// Is this step in the DB? If it is, we use that if ($firstStep->ID == $this->getCurrentStep()->ID) {
$nextStep = $step->getNextStepFromDatabase(); $this->currentStepHasBeenFound = true;
if(!$nextStep) { } else {
// If it's not in the DB, we use a singleton instance of it instead - $firstStep->addExtraClass('completed');
// - this step hasn't been accessed yet }
$nextStep = singleton($step->getNextStep());
} $this->getAllStepsRecursive($firstStep, $stepsFound);
// once the current steps has been found we won't add the completed class anymore. return $stepsFound;
if ($nextStep->ID == $this->getCurrentStep()->ID) $this->currentStepHasBeenFound = true; }
$nextStep->LinkingMode = ($nextStep->ID == $this->getCurrentStep()->ID) ? 'current' : 'link'; /**
* Recursively run through steps using the getNextStep() method on each step
// add the completed class * to determine what the next step is, gathering each step along the way.
if (!$this->currentStepHasBeenFound) $nextStep->addExtraClass('completed'); * 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
$nextStep->setForm($this); * 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.
// Add the array data, and do a callback *
$stepsFound->push($nextStep); * @param $step Subclass of MultiFormStep to find the next step of
$this->getAllStepsRecursive($nextStep, $stepsFound); * @param $stepsFound $stepsFound DataObjectSet reference, the steps found to call back on
} * @return DataObjectSet of MultiFormStep instances
// Once we've reached the final step, we just return what we've collected */
} else { protected function getAllStepsRecursive($step, &$stepsFound)
return $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
* Number of steps already completed (excluding currently started step). $nextStep = $step->getNextStepFromDatabase();
* The way we determine a step is complete is to check if it has the Data if (!$nextStep) {
* field filled out with a serialized value, then we know that the user has // If it's not in the DB, we use a singleton instance of it instead -
* clicked next on the given step, to proceed. // - this step hasn't been accessed yet
* $nextStep = singleton($step->getNextStep());
* @TODO Not sure if it's entirely appropriate to check if Data is set as a }
* way to determine a step is "completed".
* // once the current steps has been found we won't add the completed class anymore.
* @return int if ($nextStep->ID == $this->getCurrentStep()->ID) {
*/ $this->currentStepHasBeenFound = true;
public function getCompletedStepCount() { }
$steps = DataObject::get('MultiFormStep', "\"SessionID\" = {$this->session->ID} && \"Data\" IS NOT NULL");
$nextStep->LinkingMode = ($nextStep->ID == $this->getCurrentStep()->ID) ? 'current' : 'link';
return $steps ? $steps->Count() : 0;
} // add the completed class
if (!$this->currentStepHasBeenFound) {
/** $nextStep->addExtraClass('completed');
* 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. $nextStep->setForm($this);
*
* @return int // Add the array data, and do a callback
*/ $stepsFound->push($nextStep);
public function getTotalStepCount() { $this->getAllStepsRecursive($nextStep, $stepsFound);
return $this->getAllStepsLinear() ? $this->getAllStepsLinear()->Count() : 0; }
} // Once we've reached the final step, we just return what we've collected
} else {
/** return $stepsFound;
* Percentage of steps completed (excluding currently started step) }
* }
* @return float
*/ /**
public function getCompletedPercent() { * Number of steps already completed (excluding currently started step).
return (float) $this->getCompletedStepCount() * 100 / $this->getTotalStepCount(); * 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->getCompletedStepCount() * 100 / $this->getTotalStepCount();
}
} }

View File

@ -10,59 +10,66 @@
* *
* @package multiform * @package multiform
*/ */
class MultiFormSession extends DataObject { class MultiFormSession extends DataObject
{
private static $db = array( private static $db = array(
'Hash' => 'Varchar(40)', // cryptographic hash identification to this session 'Hash' => 'Varchar(40)', // cryptographic hash identification to this session
'IsComplete' => 'Boolean' // flag to determine if this session is marked completed 'IsComplete' => 'Boolean' // flag to determine if this session is marked completed
); );
private static $has_one = array( private static $has_one = array(
'Submitter' => 'Member', 'Submitter' => 'Member',
'CurrentStep' => 'MultiFormStep' 'CurrentStep' => 'MultiFormStep'
); );
private static $has_many = array( private static $has_many = array(
'FormSteps' => 'MultiFormStep' 'FormSteps' => 'MultiFormStep'
); );
/** /**
* Mark this session as completed. * Mark this session as completed.
* *
* This sets the flag "IsComplete" to true, * This sets the flag "IsComplete" to true,
* and writes the session back. * and writes the session back.
*/ */
public function markCompleted() { public function markCompleted()
$this->IsComplete = 1; {
$this->write(); $this->IsComplete = 1;
} $this->write();
}
/** /**
* These actions are performed when write() is called on this object. * These actions are performed when write() is called on this object.
*/ */
public function onBeforeWrite() { public function onBeforeWrite()
// save submitter if a Member is logged in {
$currentMember = Member::currentUser(); // save submitter if a Member is logged in
if(!$this->SubmitterID && $currentMember) $this->SubmitterID = $currentMember->ID; $currentMember = Member::currentUser();
if (!$this->SubmitterID && $currentMember) {
$this->SubmitterID = $currentMember->ID;
}
parent::onBeforeWrite(); parent::onBeforeWrite();
} }
/** /**
* These actions are performed when delete() is called on this object. * These actions are performed when delete() is called on this object.
*/ */
public function onBeforeDelete() { public function onBeforeDelete()
// delete dependent form steps and relation {
$steps = $this->FormSteps(); // delete dependent form steps and relation
if($steps) foreach($steps as $step) { $steps = $this->FormSteps();
if($step && $step->exists()) { if ($steps) {
$steps->remove($step); foreach ($steps as $step) {
$step->delete(); if ($step && $step->exists()) {
$step->destroy(); $steps->remove($step);
} $step->delete();
} $step->destroy();
}
parent::onBeforeDelete(); }
} }
parent::onBeforeDelete();
}
} }

View File

@ -9,457 +9,489 @@
* *
* @package multiform * @package multiform
*/ */
class MultiFormStep extends DataObject { class MultiFormStep extends DataObject
{
private static $db = array( private static $db = array(
'Data' => 'Text' // stores serialized maps with all session information 'Data' => 'Text' // stores serialized maps with all session information
); );
private static $has_one = array( private static $has_one = array(
'Session' => 'MultiFormSession' 'Session' => 'MultiFormSession'
); );
/** /**
* Centerpiece of the flow control for the form. * 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
*/
public 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
*/
public 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;
/**
* List of additional CSS classes for this step
*
* @var array $extraClasses
*/
protected $extraClasses = array();
/**
* Temporary cache to increase the performance for repeated look ups.
*
* @var array $cache
*/
protected $step_data_cache = array();
/**
* 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 FieldList
*/
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 FieldList
*/
public function getExtraActions() {
return (class_exists('FieldList')) ? new FieldList() : 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 ? $this->title : $this->class;
}
/**
* 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() {
$form = $this->form;
return Controller::join_links($form->getDisplayLink(), "?{$form->config()->get_var}={$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 ($this->Data && is_string($this->Data)) ? unserialize($this->Data) : array();
}
/**
* 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(
Controller::curr(),
'Form',
$this->getFields(),
((class_exists('FieldList')) ? new FieldList() : 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 = static::$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 && 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;
}
}
}
/**
* Accessor method for self::$next_steps
*
* @return string|array
*/
public function getNextSteps() {
return static::$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) {
$step->setForm($this->form);
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}");
}
}
/**
* 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;
}
/**
* @return Form
*/
public function getForm() {
return $this->form;
}
// ##################### Utility ####################
/**
* 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.
*
* @return boolean
*/
public function canGoBack() {
return static::$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 static::$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;
}
/**
* 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));
}
/**
* Returns the submitted value, if any, of any steps.
*
* @param string $fromStep (classname)
* @param string $key
* *
* @return mixed * If set to a string, you have a linear form flow
*/ * If set to an array, you should use {@link getNextStep()}
public function getValueFromOtherStep($fromStep, $key) { * to enact flow control and branching to different form
// load the steps in the cache, if this one doesn't exist * steps, most likely based on previously set session data
if (!array_key_exists('steps_' . $fromStep, $this->step_data_cache)) { * (e.g. a checkbox field or a dropdown).
$steps = MultiFormStep::get()->filter('SessionID', $this->form->session->ID); *
* @var array|string
*/
public static $next_steps;
if($steps) { /**
foreach($steps as $step) { * Each {@link MultiForm} subclass needs at least
$this->step_data_cache['steps_' . $step->ClassName] = $step->loadData(); * one step which is marked as the "final" one
* and triggers the {@link MultiForm->finish()}
* method that wraps up the whole submission.
*
* @var boolean
*/
public 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;
/**
* List of additional CSS classes for this step
*
* @var array $extraClasses
*/
protected $extraClasses = array();
/**
* Temporary cache to increase the performance for repeated look ups.
*
* @var array $cache
*/
protected $step_data_cache = array();
/**
* 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 FieldList
*/
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 FieldList
*/
public function getExtraActions()
{
return (class_exists('FieldList')) ? new FieldList() : 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 ? $this->title : $this->class;
}
/**
* 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()
{
$form = $this->form;
return Controller::join_links($form->getDisplayLink(), "?{$form->config()->get_var}={$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 ($this->Data && is_string($this->Data)) ? unserialize($this->Data) : array();
}
/**
* 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(
Controller::curr(),
'Form',
$this->getFields(),
((class_exists('FieldList')) ? new FieldList() : 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 = static::$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 && 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;
}
}
}
/**
* Accessor method for self::$next_steps
*
* @return string|array
*/
public function getNextSteps()
{
return static::$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) {
$step->setForm($this->form);
if ($step->getNextStep()) {
if ($step->getNextStep() == $this->class) {
return $step->class;
}
} }
} }
} }
}
// check both as PHP isn't recursive /**
if(isset($this->step_data_cache['steps_' . $fromStep])) { * Retrieves the previous step class record from the database.
if(isset($this->step_data_cache['steps_' . $fromStep][$key])) { *
* 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;
}
/**
* @return Form
*/
public function getForm()
{
return $this->form;
}
// ##################### Utility ####################
/**
* 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.
*
* @return boolean
*/
public function canGoBack()
{
return static::$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 static::$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;
}
/**
* 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));
}
/**
* Returns the submitted value, if any, of any steps.
*
* @param string $fromStep (classname)
* @param string $key
*
* @return mixed
*/
public function getValueFromOtherStep($fromStep, $key)
{
// load the steps in the cache, if this one doesn't exist
if (!array_key_exists('steps_' . $fromStep, $this->step_data_cache)) {
$steps = MultiFormStep::get()->filter('SessionID', $this->form->session->ID);
if ($steps) {
foreach ($steps as $step) {
$this->step_data_cache['steps_' . $step->ClassName] = $step->loadData();
}
}
}
// check both as PHP isn't recursive
if (isset($this->step_data_cache['steps_' . $fromStep])) {
if (isset($this->step_data_cache['steps_' . $fromStep][$key])) {
return $this->step_data_cache['steps_' . $fromStep][$key]; return $this->step_data_cache['steps_' . $fromStep][$key];
} }
} }
return null; return null;
} }
/** /**
* allows to get a value from another step copied over * allows to get a value from another step copied over
* *
* @param FieldList $fields * @param FieldList $fields
* @param string $formStep * @param string $formStep
* @param string $fieldName * @param string $fieldName
* @param string $fieldNameTarget (optional) * @param string $fieldNameTarget (optional)
*/ */
public function copyValueFromOtherStep(FieldList $fields, $formStep, $fieldName, $fieldNameTarget = null) { public function copyValueFromOtherStep(FieldList $fields, $formStep, $fieldName, $fieldNameTarget = null)
// if a target field isn't defined use the same fieldname {
if (!$fieldNameTarget) $fieldNameTarget = $fieldName; // if a target field isn't defined use the same fieldname
if (!$fieldNameTarget) {
$fieldNameTarget = $fieldName;
}
$fields->fieldByName($fieldNameTarget)->setValue($this->getValueFromOtherStep($formStep, $fieldName)); $fields->fieldByName($fieldNameTarget)->setValue($this->getValueFromOtherStep($formStep, $fieldName));
} }
} }

View File

@ -12,44 +12,48 @@
* *
* @package multiform * @package multiform
*/ */
class MultiFormPurgeTask extends BuildTask { class MultiFormPurgeTask extends BuildTask
{
/** /**
* Days after which sessions expire and * Days after which sessions expire and
* are automatically deleted. * are automatically deleted.
* *
* @var int * @var int
*/ */
public static $session_expiry_days = 7; public static $session_expiry_days = 7;
/** /**
* Run this cron task. * Run this cron task.
* *
* Go through all MultiFormSession records that * Go through all MultiFormSession records that
* are older than the days specified in $session_expiry_days * are older than the days specified in $session_expiry_days
* and delete them. * and delete them.
*/ */
public function run($request) { public function run($request)
$sessions = $this->getExpiredSessions(); {
$delCount = 0; $sessions = $this->getExpiredSessions();
if($sessions) foreach($sessions as $session) { $delCount = 0;
$session->delete(); if ($sessions) {
$delCount++; foreach ($sessions as $session) {
} $session->delete();
echo $delCount . ' session records deleted that were older than ' . self::$session_expiry_days . ' days.'; $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
);
}
/**
* 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
);
}
} }

View File

@ -1,44 +1,45 @@
<?php <?php
class MultiFormObjectDecoratorTest extends SapphireTest { class MultiFormObjectDecoratorTest extends SapphireTest
{
protected static $fixture_file = 'MultiFormObjectDecoratorTest.yml'; protected static $fixture_file = 'MultiFormObjectDecoratorTest.yml';
protected $requiredExtensions = array( protected $requiredExtensions = array(
'MultiFormObjectDecorator_DataObject' => array('MultiFormObjectDecorator') 'MultiFormObjectDecorator_DataObject' => array('MultiFormObjectDecorator')
); );
protected $extraDataObjects = array( protected $extraDataObjects = array(
'MultiFormObjectDecorator_DataObject' 'MultiFormObjectDecorator_DataObject'
); );
public function testTemporaryDataFilteredQuery() { public function testTemporaryDataFilteredQuery()
$records = MultiFormObjectDecorator_DataObject::get() {
->map('Name') $records = MultiFormObjectDecorator_DataObject::get()
->toArray(); ->map('Name')
->toArray();
$this->assertContains('Test 1', $records); $this->assertContains('Test 1', $records);
$this->assertContains('Test 2', $records); $this->assertContains('Test 2', $records);
$this->assertNotContains('Test 3', $records); $this->assertNotContains('Test 3', $records);
}
} public function testTemporaryDataQuery()
{
public function testTemporaryDataQuery() { $records = MultiFormObjectDecorator_DataObject::get()
$records = MultiFormObjectDecorator_DataObject::get() ->filter(array('MultiFormIsTemporary' => 1))
->filter(array('MultiFormIsTemporary' => 1)) ->map('Name')
->map('Name') ->toArray();
->toArray();
$this->assertNotContains('Test 1', $records);
$this->assertNotContains('Test 2', $records);
$this->assertContains('Test 3', $records);
}
$this->assertNotContains('Test 1', $records);
$this->assertNotContains('Test 2', $records);
$this->assertContains('Test 3', $records);
}
} }
class MultiFormObjectDecorator_DataObject extends DataObject { class MultiFormObjectDecorator_DataObject extends DataObject
{
private static $db = array(
'Name' => 'Varchar'
);
private static $db = array(
'Name' => 'Varchar'
);
} }

View File

@ -17,162 +17,181 @@
* @package multiform * @package multiform
* @subpackage tests * @subpackage tests
*/ */
class MultiFormTest extends FunctionalTest { class MultiFormTest extends FunctionalTest
{
public static $fixture_file = 'multiform/tests/MultiFormTest.yml'; public static $fixture_file = 'multiform/tests/MultiFormTest.yml';
protected $controller; protected $controller;
public function setUp() { public function setUp()
parent::setUp(); {
parent::setUp();
$this->controller = new MultiFormTest_Controller(); $this->controller = new MultiFormTest_Controller();
$this->form = $this->controller->Form(); $this->form = $this->controller->Form();
} }
public function testInitialisingForm() { public 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->assertTrue(is_numeric($this->form->getCurrentStep()->ID) && ($this->form->getCurrentStep()->ID > 0));
$this->assertEquals('MultiFormTest_StepOne', $this->form->getStartStep()); $this->assertTrue(is_numeric($this->form->getSession()->ID) && ($this->form->getSession()->ID > 0));
} $this->assertEquals('MultiFormTest_StepOne', $this->form->getStartStep());
}
public function testSessionGeneration() { public function testSessionGeneration()
$this->assertTrue($this->form->session->ID > 0); {
} $this->assertTrue($this->form->session->ID > 0);
}
public function testMemberLogging() { public function testMemberLogging()
// Grab any user to fake being logged in as, and ensure that after a session is written it has {
// that user as the submitter. // Grab any user to fake being logged in as, and ensure that after a session is written it has
$userId = Member::get_one("Member")->ID; // that user as the submitter.
$this->session()->inst_set('loggedInAs', $userId); $userId = Member::get_one("Member")->ID;
$this->session()->inst_set('loggedInAs', $userId);
$session = $this->form->session; $session = $this->form->session;
$session->write(); $session->write();
$this->assertEquals($userId, $session->SubmitterID); $this->assertEquals($userId, $session->SubmitterID);
} }
public function testSecondStep() { public function testSecondStep()
$this->assertEquals('MultiFormTest_StepTwo', $this->form->getCurrentStep()->getNextStep()); {
} $this->assertEquals('MultiFormTest_StepTwo', $this->form->getCurrentStep()->getNextStep());
}
public function testParentForm() { public function testParentForm()
$currentStep = $this->form->getCurrentStep(); {
$this->assertEquals($currentStep->getForm()->class, $this->form->class); $currentStep = $this->form->getCurrentStep();
} $this->assertEquals($currentStep->getForm()->class, $this->form->class);
}
public function testTotalStepCount() { public function testTotalStepCount()
$this->assertEquals(3, $this->form->getAllStepsLinear()->Count()); {
} $this->assertEquals(3, $this->form->getAllStepsLinear()->Count());
}
public function testCompletedSession() { public function testCompletedSession()
$this->form->setCurrentSessionHash($this->form->session->Hash); {
$this->assertInstanceOf('MultiFormSession', $this->form->getCurrentSession()); $this->form->setCurrentSessionHash($this->form->session->Hash);
$this->form->session->markCompleted(); $this->assertInstanceOf('MultiFormSession', $this->form->getCurrentSession());
$this->assertNull($this->form->getCurrentSession()); $this->form->session->markCompleted();
} $this->assertNull($this->form->getCurrentSession());
}
public function testIncorrectSessionIdentifier() { public function testIncorrectSessionIdentifier()
$this->form->setCurrentSessionHash('sdfsdf3432325325sfsdfdf'); // made up! {
$this->form->setCurrentSessionHash('sdfsdf3432325325sfsdfdf'); // made up!
// A new session is generated, even though we made up the identifier // A new session is generated, even though we made up the identifier
$this->assertInstanceOf('MultiFormSession', $this->form->session); $this->assertInstanceOf('MultiFormSession', $this->form->session);
} }
function testCustomGetVar() { function testCustomGetVar()
Config::nest(); {
Config::inst()->update('MultiForm', 'get_var', 'SuperSessionID'); Config::nest();
Config::inst()->update('MultiForm', 'get_var', 'SuperSessionID');
$form = $this->controller->Form(); $form = $this->controller->Form();
$this->assertContains('SuperSessionID', $form::$ignored_fields, "GET var wasn't added to ignored fields"); $this->assertContains('SuperSessionID', $form::$ignored_fields, "GET var wasn't added to ignored fields");
$this->assertContains('SuperSessionID', $form->FormAction(), "Form action doesn't contain correct session $this->assertContains('SuperSessionID', $form->FormAction(), "Form action doesn't contain correct session
ID parameter"); ID parameter");
$this->assertContains('SuperSessionID', $form->getCurrentStep()->Link(), "Form step doesn't contain correct $this->assertContains('SuperSessionID', $form->getCurrentStep()->Link(), "Form step doesn't contain correct
session ID parameter"); session ID parameter");
Config::unnest(); Config::unnest();
} }
} }
/** /**
* @package multiform * @package multiform
* @subpackage tests * @subpackage tests
*/ */
class MultiFormTest_Controller extends Controller implements TestOnly { class MultiFormTest_Controller extends Controller implements TestOnly
{
public function Link() { public function Link()
return 'MultiFormTest_Controller'; {
} return 'MultiFormTest_Controller';
}
public function Form($request = null) { public function Form($request = null)
$form = new MultiFormTest_Form($this, 'Form'); {
$form->setHTMLID('MultiFormTest_Form'); $form = new MultiFormTest_Form($this, 'Form');
return $form; $form->setHTMLID('MultiFormTest_Form');
} return $form;
}
} }
/** /**
* @package multiform * @package multiform
* @subpackage tests * @subpackage tests
*/ */
class MultiFormTest_Form extends MultiForm implements TestOnly { class MultiFormTest_Form extends MultiForm implements TestOnly
{
public static $start_step = 'MultiFormTest_StepOne'; public static $start_step = 'MultiFormTest_StepOne';
public function getStartStep() {
return self::$start_step;
}
public function getStartStep()
{
return self::$start_step;
}
} }
/** /**
* @package multiform * @package multiform
* @subpackage tests * @subpackage tests
*/ */
class MultiFormTest_StepOne extends MultiFormStep implements TestOnly { class MultiFormTest_StepOne extends MultiFormStep implements TestOnly
{
public static $next_steps = 'MultiFormTest_StepTwo'; public static $next_steps = 'MultiFormTest_StepTwo';
public function getFields() { public function getFields()
$class = (class_exists('FieldList')) ? 'FieldList' : 'FieldSet'; {
return new $class( $class = (class_exists('FieldList')) ? 'FieldList' : 'FieldSet';
new TextField('FirstName', 'First name'), return new $class(
new TextField('Surname', 'Surname'), new TextField('FirstName', 'First name'),
new EmailField('Email', 'Email address') new TextField('Surname', 'Surname'),
); new EmailField('Email', 'Email address')
} );
}
} }
/** /**
* @package multiform * @package multiform
* @subpackage tests * @subpackage tests
*/ */
class MultiFormTest_StepTwo extends MultiFormStep implements TestOnly { class MultiFormTest_StepTwo extends MultiFormStep implements TestOnly
{
public static $next_steps = 'MultiFormTest_StepThree'; public static $next_steps = 'MultiFormTest_StepThree';
public function getFields() { public function getFields()
$class = (class_exists('FieldList')) ? 'FieldList' : 'FieldSet'; {
return new $class( $class = (class_exists('FieldList')) ? 'FieldList' : 'FieldSet';
new TextareaField('Comments', 'Tell us a bit about yourself...') return new $class(
); new TextareaField('Comments', 'Tell us a bit about yourself...')
} );
}
} }
/** /**
* @package multiform * @package multiform
* @subpackage tests * @subpackage tests
*/ */
class MultiFormTest_StepThree extends MultiFormStep implements TestOnly { class MultiFormTest_StepThree extends MultiFormStep implements TestOnly
{
public static $is_final_step = true; public static $is_final_step = true;
public function getFields() {
$class = (class_exists('FieldList')) ? 'FieldList' : 'FieldSet';
return new $class(
new TextField('Test', 'Anything else you\'d like to tell us?')
);
}
public function getFields()
{
$class = (class_exists('FieldList')) ? 'FieldList' : 'FieldSet';
return new $class(
new TextField('Test', 'Anything else you\'d like to tell us?')
);
}
} }