ENHANCEMENT: added test coverage for UserDefinedForm.php and placeholders for other sections. API change: processNewFormFields removed

API change: refactored Form() into getFormFields(), getFormActions(), getRequiredFields()

ENHANCEMENT: added updateForm* extension hooks to allow customization of userforms
BUGFIX: gave custom scripts their own ID to enable it to be blocked.
This commit is contained in:
Will Rossiter 2010-09-03 05:06:13 +00:00
parent 5f4d4e7d28
commit 3c1d81d014
23 changed files with 1121 additions and 435 deletions

View File

@ -19,7 +19,7 @@ class UserDefinedForm extends Page {
/** /**
* @var String What level permission is needed to edit / add * @var String What level permission is needed to edit / add
*/ */
static $need_permission = 'ADMIN'; static $need_permission = array('ADMIN');
/** /**
* @var String Required Identifier * @var String Required Identifier
@ -68,6 +68,7 @@ class UserDefinedForm extends Page {
// define tabs // define tabs
$fields->findOrMakeTab('Root.Content.Form', _t('UserDefinedForm.FORM', 'Form')); $fields->findOrMakeTab('Root.Content.Form', _t('UserDefinedForm.FORM', 'Form'));
$fields->findOrMakeTab('Root.Content.Options', _t('UserDefinedForm.OPTIONS', 'Options'));
$fields->findOrMakeTab('Root.Content.EmailRecipients', _t('UserDefinedForm.EMAILRECIPIENTS', 'Email Recipients')); $fields->findOrMakeTab('Root.Content.EmailRecipients', _t('UserDefinedForm.EMAILRECIPIENTS', 'Email Recipients'));
$fields->findOrMakeTab('Root.Content.OnComplete', _t('UserDefinedForm.ONCOMPLETE', 'On Complete')); $fields->findOrMakeTab('Root.Content.OnComplete', _t('UserDefinedForm.ONCOMPLETE', 'On Complete'));
$fields->findOrMakeTab('Root.Content.Submissions', _t('UserDefinedForm.SUBMISSIONS', 'Submissions')); $fields->findOrMakeTab('Root.Content.Submissions', _t('UserDefinedForm.SUBMISSIONS', 'Submissions'));
@ -102,6 +103,7 @@ class UserDefinedForm extends Page {
); );
$fields->addFieldsToTab("Root.Content.OnComplete", $onCompleteFieldSet); $fields->addFieldsToTab("Root.Content.OnComplete", $onCompleteFieldSet);
$fields->addFieldsToTab("Root.Content.Options", $this->getFormOptions());
return $fields; return $fields;
} }
@ -155,19 +157,61 @@ class UserDefinedForm extends Page {
} }
/** /**
* Roll back a form to a previous version * Roll back a form to a previous version.
* *
* @param String|int Version to roll back to * @param String|int Version to roll back to
*/ */
public function doRollbackTo($version) { public function doRollbackTo($version) {
parent::doRollbackTo($version);
/*
Not implemented yet
// get the older version
$reverted = Versioned::get_version($this->ClassName, $this->ID, $version);
if($reverted) {
// using the lastedited date of the reverted object we can work out which
// form fields to revert back to
if($this->Fields()) { if($this->Fields()) {
foreach($this->Fields() as $field) { foreach($this->Fields() as $field) {
$field->publish($version, "Stage", true); // query to see when the version of the page was pumped
$editedDate = DB::query("
SELECT LastEdited
FROM \"SiteTree_versions\"
WHERE \"RecordID\" = '$this->ID' AND \"Version\" = $version
")->value();
// find a the latest version which has been edited
$versionToGet = DB::query("
SELECT *
FROM \"EditableFormField_versions\"
WHERE \"RecordID\" = '$field->ID' AND \"LastEdited\" <= '$editedDate'
ORDER BY Version DESC
LIMIT 1
")->record();
if($versionToGet) {
Debug::show('publishing field'. $field->Name);
Debug::show($versionToGet);
$field->publish($versionToGet, "Stage", true);
$field->writeWithoutVersion(); $field->writeWithoutVersion();
} }
else {
Debug::show('deleting field'. $field->Name);
$this->Fields()->remove($field);
$field->delete();
$field->destroy();
}
}
} }
parent::doRollbackTo($version); // @todo Emails
}
*/
} }
/** /**
@ -178,7 +222,8 @@ class UserDefinedForm extends Page {
public function doRevertToLive() { public function doRevertToLive() {
if($this->Fields()) { if($this->Fields()) {
foreach($this->Fields() as $field) { foreach($this->Fields() as $field) {
$field->writeToStage('Live', 'Stage'); $field->publish("Live", "Stage", false);
$field->writeWithoutVersion();
} }
} }
@ -216,16 +261,22 @@ class UserDefinedForm extends Page {
} }
/** /**
* Custom Form Actions for the form * Custom options for the form. You can extend the built in options by
* using {@link updateFormOptions()}
* *
* @param bool Is the Form readonly
* @return FieldSet * @return FieldSet
*/ */
public function customFormActions($isReadonly = false) { public function getFormOptions() {
return new FieldSet( $submit = ($this->SubmitButtonText) ? $this->SubmitButtonText : _t('UserDefinedForm.SUBMITBUTTON', 'Submit');
new TextField("SubmitButtonText", _t('UserDefinedForm.TEXTONSUBMIT', 'Text on submit button:'), $this->SubmitButtonText),
$options = new FieldSet(
new TextField("SubmitButtonText", _t('UserDefinedForm.TEXTONSUBMIT', 'Text on submit button:'), $submit),
new CheckboxField("ShowClearButton", _t('UserDefinedForm.SHOWCLEARFORM', 'Show Clear Form Button'), $this->ShowClearButton) new CheckboxField("ShowClearButton", _t('UserDefinedForm.SHOWCLEARFORM', 'Show Clear Form Button'), $this->ShowClearButton)
); );
$this->extend('updateFormOptions', $options);
return $options;
} }
/** /**
@ -286,69 +337,168 @@ class UserDefinedForm_Controller extends Page_Controller {
return array( return array(
'Content' => DBField::create('HTMLText', $this->Content), 'Content' => DBField::create('HTMLText', $this->Content),
'Form' => $this->Form 'Form' => $this->Form()
); );
} }
/** /**
* User Defined Form. Feature of the user defined form is if you want the * Get the form for the page. Form can be modified by calling {@link updateForm()}
* form to appear in a custom location on the page you can use $UserDefinedForm * on a UserDefinedForm extension
* in the content area to describe where you want the form
*
* @todo Abstract the Conditional Logic from the Form. This should be tied
* to the EditableFormField class so that fields (eg checkboxes)
* can define their own logic
* *
* @return Form * @return Form
*/ */
public function Form() { function Form() {
$fields = $this->getFormFields();
$actions = $this->getFormActions();
// get the required fields including the validation
$required = $this->getRequiredFields();
// generate the conditional logic
$this->generateConditionalJavascript();
$form = new Form($this, "Form", $fields, $actions, $required);
$this->extend('updateForm', $form);
return $form;
}
/**
* Get the form fields for the form on this page. Can modify this FieldSet
* by using {@link updateFormFields()} on an {@link Extension} subclass which
* is applied to this controller
*
* @return FieldSet
*/
function getFormFields() {
$fields = new FieldSet(); $fields = new FieldSet();
$fieldValidation = array();
$fieldValidationRules = array(); if($this->Fields()) {
$customDisplayRules = ""; foreach($this->Fields() as $editableField) {
$defaults = ""; // get the raw form field from the editable version
$this->SubmitButtonText = ($this->SubmitButtonText) ? $this->SubmitButtonText : _t('UserDefinedForm.SUBMITBUTTON', 'Submit'); $field = $editableField->getFormField();
if(!$field) break;
// set the error / formatting messages
$title = strip_tags("'". ($editableField->Title ? $editableField->Title : $editableField->Name) . "'");
$errorMessage = sprintf(_t('Form.FIELDISREQUIRED', '%s is required').'.', $title);
$errorMessage = ($editableField->CustomErrorMessage) ? $editableField->CustomErrorMessage : $errorMessage;
$field->setCustomValidationMessage($errorMessage);
// set the right title on this field
if($right = $editableField->getSetting('RightTitle')) {
$field->setRightTitle($right);
}
// if this field is required add some
if($editableField->Required) {
$field->addExtraClass('requiredField');
if($identifier = UserDefinedForm::$required_identifier) {
$title = $field->Title() ." <span class='required-identifier'>". $identifier . "</span>";
$field->setTitle($title);
}
}
$fields->push($field);
}
}
$this->extend('updateFormFields', $fields);
return $fields;
}
/**
* Generate the form actions for the UserDefinedForm. You
* can manipulate these by using {@link updateFormActions()} on
* a decorator.
*
* @todo Make form actions editable via their own field editor.
*
* @return FieldSet
*/
function getFormActions() {
$submitText = ($this->SubmitButtonText) ? $this->SubmitButtonText : _t('UserDefinedForm.SUBMITBUTTON', 'Submit');
$actions = new FieldSet(
new FormAction("process", $submitText)
);
if($this->ShowClearButton) {
$actions->push(new ResetFormAction("clearForm"));
}
$this->extend('updateFormActions', $actions);
return $actions;
}
/**
* Get the required form fields for this form. Includes building the jQuery
* validate structure
*
* @return RequiredFields
*/
function getRequiredFields() {
$required = new RequiredFields();
$rules = array();
$validation = array();
if($this->Fields()) { if($this->Fields()) {
foreach($this->Fields() as $field) { foreach($this->Fields() as $field) {
$fieldToAdd = $field->getFormField();
if(!$fieldToAdd) break;
$fieldValidationOptions = array();
// Set the Error Messages
$errorMessage = sprintf(_t('Form.FIELDISREQUIRED', '%s is required').'.', strip_tags("'". ($field->Title ? $field->Title : $field->Name) . "'"));
$errorMessage = ($field->CustomErrorMessage) ? $field->CustomErrorMessage : $errorMessage;
$fieldToAdd->setCustomValidationMessage($errorMessage);
// Set the right title on this field
if($right = $field->getSetting('RightTitle')) {
$fieldToAdd->setRightTitle($right);
}
// Is this field required
if($field->Required) { if($field->Required) {
$fieldValidation[$field->Name] = $errorMessage;
$fieldValidationOptions['required'] = true; $validation[$field->Name] = $field->getFormField()->getCustomValidationMessage();
$fieldToAdd->addExtraClass('requiredField'); $rules[$field->Name] = array_merge(array('required'), $field->getValidation());
if(UserDefinedForm::$required_identifier) {
$title = $fieldToAdd->Title() ." <span class='requiredIdentifier'>". UserDefinedForm::$required_identifier . "</span>"; $required->addRequiredField($field->Name);
$fieldToAdd->setTitle($title); }
} }
} }
// Add field to the form // Set the Form Name
$fields->push($fieldToAdd); $rules = $this->array2json($rules);
$messages = $this->array2json($validation);
// Ask our form field for some more information on hour it should be validated // set the custom script for this form
$fieldValidationOptions = array_merge($fieldValidationOptions, $field->getValidation()); Requirements::customScript(<<<JS
(function($) {
$(document).ready(function() {
$("#Form_Form").validate({
errorClass: "required",
messages:
$messages
,
rules:
$rules
});
});
})(jQuery);
JS
, 'UserFormsValidation');
// Check if we have need to update the global validation $this->extend('updateRequiredFields', $required);
if($fieldValidationOptions) {
$fieldValidationRules[$field->Name] = $fieldValidationOptions; return $required;
} }
/**
* Generate the javascript for the conditional field show / hiding logic.
* Allows complex rules to be created
* @return void
*/
function generateConditionalJavascript() {
$default = "";
$rules = "";
if($this->Fields()) {
foreach($this->Fields() as $field) {
$fieldId = $field->Name; $fieldId = $field->Name;
if($field->ClassName == 'EditableFormHeading') { if($field->ClassName == 'EditableFormHeading') {
@ -356,8 +506,8 @@ class UserDefinedForm_Controller extends Page_Controller {
} }
// Is this Field Show by Default // Is this Field Show by Default
if(!$field->ShowOnLoad) { if(!$field->getShowOnLoad()) {
$defaults .= "$(\"#" . $fieldId . "\").hide();\n"; $default .= "$(\"#" . $fieldId . "\").hide();\n";
} }
// Check for field dependencies / default // Check for field dependencies / default
@ -438,7 +588,7 @@ class UserDefinedForm_Controller extends Page_Controller {
break; break;
} }
// put it all together // put it all together
$customDisplayRules .= $fieldToWatch.".$action(function() { $rules .= $fieldToWatch.".$action(function() {
if(". $expression ." ) { if(". $expression ." ) {
$(\"#". $fieldId ."\").".$view."(); $(\"#". $fieldId ."\").".$view."();
} }
@ -451,53 +601,18 @@ class UserDefinedForm_Controller extends Page_Controller {
} }
} }
} }
$referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
// Keep track of the referer
$fields->push( new HiddenField( "Referrer", "", $referer ) );
// Build actions
$actions = new FieldSet(
new FormAction("process", $this->SubmitButtonText)
);
// Do we want to add a clear form.
if($this->ShowClearButton) {
$actions->push(new ResetFormAction("clearForm"));
}
// return the form
$form = new Form($this, "Form", $fields, $actions, new RequiredFields(array_keys($fieldValidation)));
$form->loadDataFrom($this->failover);
$FormName = $form->FormName();
// Set the Form Name
$rules = $this->array2json($fieldValidationRules);
$messages = $this->array2json($fieldValidation);
// set the custom script for this form
Requirements::customScript(<<<JS Requirements::customScript(<<<JS
(function($) { (function($) {
$(document).ready(function() { $(document).ready(function() {
$defaults
$("#$FormName").validate({
errorClass: "required",
messages:
$messages
,
rules:
$rules $rules
});
$customDisplayRules $default
}); })
})(jQuery); })(jQuery);
JS JS
); , 'UserFormsConditional');
return $form;
} }
/** /**
@ -507,15 +622,15 @@ JS
* @param Array array to convert * @param Array array to convert
* @return JSON * @return JSON
*/ */
protected function array2json($array) { function array2json($array) {
foreach($array as $key => $value) foreach($array as $key => $value)
if(is_array( $value )) { if(is_array( $value )) {
$result[] = "$key:" . $this->array2json($value); $result[] = "$key:" . $this->array2json($value);
} else { } else {
$value = (is_bool($value)) ? $value : "\"$value\""; $value = (is_bool($value)) ? $value : "\"$value\"";
$result[] = "$key:$value \n"; $result[] = "$key:$value";
} }
return (isset($result)) ? "{\n".implode( ', ', $result ) ."} \n": '{}'; return (isset($result)) ? "{\n".implode( ', ', $result ) ."\n}\n": '{}';
} }
/** /**
@ -526,30 +641,30 @@ JS
* @return Redirection * @return Redirection
*/ */
function process($data, $form) { function process($data, $form) {
// submitted form object
$submittedForm = new SubmittedForm(); $submittedForm = Object::create('SubmittedForm');
$submittedForm->SubmittedByID = ($id = Member::currentUserID()) ? $id : 0; $submittedForm->SubmittedByID = ($id = Member::currentUserID()) ? $id : 0;
$submittedForm->ParentID = $this->ID; $submittedForm->ParentID = $this->ID;
$submittedForm->Recipient = $this->EmailTo;
// if saving is not disabled save now to generate the ID
if(!$this->DisableSaveSubmissions) $submittedForm->write(); if(!$this->DisableSaveSubmissions) $submittedForm->write();
// email values
$values = array(); $values = array();
$recipientAddresses = array();
$sendCopy = false;
$attachments = array(); $attachments = array();
$submittedFields = new DataObjectSet(); $submittedFields = new DataObjectSet();
foreach($this->Fields() as $field) { foreach($this->Fields() as $field) {
// don't show fields that shouldn't be shown
if(!$field->showInReports()) continue; if(!$field->showInReports()) continue;
// create a new submitted form field.
$submittedField = $field->getSubmittedFormField(); $submittedField = $field->getSubmittedFormField();
$submittedField->ParentID = $submittedForm->ID; $submittedField->ParentID = $submittedForm->ID;
$submittedField->Name = $field->Name; $submittedField->Name = $field->Name;
$submittedField->Title = $field->Title; $submittedField->Title = $field->Title;
// save the value from the data
if($field->hasMethod('getValueFromData')) { if($field->hasMethod('getValueFromData')) {
$submittedField->Value = $field->getValueFromData($data); $submittedField->Value = $field->getValueFromData($data);
} }
@ -564,34 +679,38 @@ JS
// create the file from post data // create the file from post data
$upload = new Upload(); $upload = new Upload();
$file = new File(); $file = new File();
$upload->loadIntoFile($_FILES[$field->Name], $file); $upload->loadIntoFile($_FILES[$field->Name], $file);
// write file to form field // write file to form field
$submittedField->UploadedFileID = $file->ID; $submittedField->UploadedFileID = $file->ID;
// Attach the file if its less than 1MB, provide a link if its over. // attach a file only if lower than 1MB
if($file->getAbsoluteSize() < 1024*1024*1){ if($file->getAbsoluteSize() < 1024*1024*1){
$attachments[] = $file; $attachments[] = $file;
} }
} }
} }
} }
if(!$this->DisableSaveSubmissions) $submittedField->write(); if(!$this->DisableSaveSubmissions) $submittedField->write();
$submittedFields->push($submittedField); $submittedFields->push($submittedField);
} }
$emailData = array( $emailData = array(
"Sender" => Member::currentUser(), "Sender" => Member::currentUser(),
"Fields" => $submittedFields "Fields" => $submittedFields
); );
// email users on submit. All have their own custom options. // email users on submit.
if($this->EmailRecipients()) { if($this->EmailRecipients()) {
$email = new UserDefinedForm_SubmittedFormEmail($submittedFields); $email = new UserDefinedForm_SubmittedFormEmail($submittedFields);
$email->populateTemplate($emailData); $email->populateTemplate($emailData);
if($attachments){ if($attachments){
foreach($attachments as $file){ foreach($attachments as $file){
// bug with double decorated fields, valid ones should have an ID.
if($file->ID != 0) { if($file->ID != 0) {
$email->attachFile($file->Filename,$file->Filename, $file->getFileType()); $email->attachFile($file->Filename,$file->Filename, $file->getFileType());
} }
@ -638,7 +757,9 @@ JS
} }
} }
return Director::redirect($this->Link() . 'finished?referrer=' . urlencode($data['Referrer'])); $referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
return Director::redirect($this->Link() . 'finished' . $referrer);
} }
/** /**
@ -656,7 +777,7 @@ JS
array( array(
'Link' => $referrer 'Link' => $referrer
))->renderWith('ReceivedFormSubmission'), ))->renderWith('ReceivedFormSubmission'),
'Form' => ' ', 'Form' => '',
)); ));
} }
} }

View File

@ -2,7 +2,7 @@
/** /**
* EditableDropdown * EditableDropdown
* *
* Represents a modifiable dropdown box on a form * Represents a modifiable dropdown (select) box on a form
* *
* @package userforms * @package userforms
*/ */
@ -13,16 +13,20 @@ class EditableDropdown extends EditableMultipleOptionField {
static $plural_name = 'Dropdowns'; static $plural_name = 'Dropdowns';
/**
* @return DropdownField
*/
function getFormField() {
function getFormField($asFilter = false) {
$optionSet = $this->Options(); $optionSet = $this->Options();
$options = array(); $options = array();
if($optionSet) { if($optionSet) {
foreach( $optionSet as $option ) { foreach($optionSet as $option) {
$options[$option->Title] = $option->Title; $options[$option->Title] = $option->Title;
} }
} }
return new DropdownField( $this->Name, $this->Title, $options);
}
return new DropdownField($this->Name, $this->Title, $options);
}
} }

View File

@ -30,33 +30,38 @@ class EditableFormField extends DataObject {
"Versioned('Stage', 'Live')" "Versioned('Stage', 'Live')"
); );
/**
* @var bool
*/
protected $readonly; protected $readonly;
/** /**
* Set this formfield to readonly * Set the visibility of an individual form field
*
* @param bool
*/ */
public function setReadonly() { public function setReadonly($readonly = true) {
$this->readonly = true; $this->readonly = $readonly;
} }
/** /**
* Is this field readonly to the user * Returns whether this field is readonly
* *
* @return bool * @return bool
*/ */
public function isReadonly() { private function isReadonly() {
return $this->readonly; return $this->readonly;
} }
/**
* Template to render the form field into
*
* @return String
*/
function EditSegment() { function EditSegment() {
return $this->renderWith('EditableFormField'); return $this->renderWith('EditableFormField');
} }
function ClassName() {
return $this->class;
}
/** /**
* Return whether a user can delete this form field * Return whether a user can delete this form field
* based on whether they can edit the page * based on whether they can edit the page
@ -64,7 +69,7 @@ class EditableFormField extends DataObject {
* @return bool * @return bool
*/ */
public function canDelete() { public function canDelete() {
return $this->Parent()->canEdit(); return ($this->Parent()->canEdit() && !$this->isReadonly());
} }
/** /**
@ -74,7 +79,7 @@ class EditableFormField extends DataObject {
* @return bool * @return bool
*/ */
public function canEdit() { public function canEdit() {
return $this->Parent()->canEdit(); return ($this->Parent()->canEdit() && !$this->isReadonly());
} }
/** /**
@ -125,6 +130,20 @@ class EditableFormField extends DataObject {
$this->CustomSettings = serialize($settings); $this->CustomSettings = serialize($settings);
} }
/**
* Set a given field setting. Appends the option to the settings or overrides
* the existing value
*
* @param String key
* @param String value
*/
public function setFieldSetting($key, $value) {
$settings = $this->getFieldSettings();
$settings[$key] = $value;
$this->setFieldSettings($settings);
}
/** /**
* Return just one custom setting or empty string if it does * Return just one custom setting or empty string if it does
* not exist * not exist
@ -147,7 +166,7 @@ class EditableFormField extends DataObject {
* *
* @return string * @return string
*/ */
public function Icon() { public function getIcon() {
return 'userforms/images/' . strtolower($this->class) . '.png'; return 'userforms/images/' . strtolower($this->class) . '.png';
} }
@ -217,11 +236,21 @@ class EditableFormField extends DataObject {
return $output; return $output;
} }
/**
* Title field of the field in the backend of the page
*
* @return TextField
*/
function TitleField() { function TitleField() {
$titleAttr = Convert::raw2att($this->Title); $titleAttr = Convert::raw2att($this->Title);
$readOnlyAttr = (!$this->canEdit()) ? ' disabled="disabled"' : ''; $readOnlyAttr = (!$this->canEdit()) ? ' disabled="disabled"' : '';
return "<input type=\"text\" class=\"text\" title=\"("._t('EditableFormField.ENTERQUESTION', 'Enter Question').")\" value=\"$titleAttr\" name=\"Fields[{$this->ID}][Title]\"$readOnlyAttr />"; $field = new TextField('Title', _t('EditableFormField.ENTERQUESTION', 'Enter Question'), $this->Title);
$field->setName($this->getFieldName('Title'));
if(!$this->canEdit()) $field->setReadonly(true);
return $field;
} }
/** /**

View File

@ -35,6 +35,9 @@ class EditableTextField extends EditableFormField {
return $fields; return $fields;
} }
/**
* @return TextareaField|TextField
*/
function getFormField() { function getFormField() {
if($this->getSetting('Rows') && $this->getSetting('Rows') > 1) { if($this->getSetting('Rows') && $this->getSetting('Rows') > 1) {
return new TextareaField($this->Name, $this->Title, $this->getSetting('Rows')); return new TextareaField($this->Name, $this->Title, $this->getSetting('Rows'));

View File

@ -1,36 +1,51 @@
<?php <?php
/** /**
* Allows CMS user to create forms dynamically. * A form field allowing a user to customize and create form fields.
* for saving into a {@link UserDefinedForm}
* *
* @package userforms * @package userforms
*/ */
class FieldEditor extends FormField { class FieldEditor extends FormField {
protected $hasFormOptions = true; /**
* Field Editor Template
*
* @return String
*/
function FieldHolder() { function FieldHolder() {
return $this->renderWith("FieldEditor"); return $this->renderWith("FieldEditor");
} }
/** /**
* Can a user edit this field? * Returns whether a user can edit the form
*
* @return boolean * @return boolean
*/ */
public function canEdit() { public function canEdit() {
if($this->readonly) return false; if($this->readonly) return false;
return $this->form->getRecord()->canEdit(); return $this->form->getRecord()->canEdit();
} }
/** /**
* Can a user delete this field? * Returns whether a user delete a field in the form. The {@link EditableFormField}s
* check if they can delete themselves but this counts as an {@link self::canEdit()}
* function rather than a delete
*
* @return boolean * @return boolean
*/ */
public function canDelete() { public function canDelete() {
if($this->readonly) return false; if($this->readonly) return false;
return $this->form->getRecord()->canDelete();
return $this->form->getRecord()->canEdit();
} }
/**
* Transform this form field to a readonly version.
*
* @return ViewableData_Customised
*/
function performReadonlyTransformation() { function performReadonlyTransformation() {
$clone = clone $this; $clone = clone $this;
$clone->readonly = true; $clone->readonly = true;
@ -43,15 +58,11 @@ class FieldEditor extends FormField {
} }
/** /**
* Return the Form Fields for the user forms * Return the fields for the user forms
* *
* @return DataObjectSet * @return DataObjectSet
*/ */
function Fields() { function Fields() {
Requirements::css("userforms/css/FieldEditor.css");
Requirements::javascript(SAPPHIRE_DIR ."/thirdparty/jquery-ui/jquery-ui-1.8rc3.custom.js");
Requirements::javascript("userforms/javascript/UserForm.js");
// Don't return any fields unless we actually have the dependent parameters set on the form field // Don't return any fields unless we actually have the dependent parameters set on the form field
if($this->form && $this->form->getRecord() && $this->name) { if($this->form && $this->form->getRecord() && $this->name) {
$relationName = $this->name; $relationName = $this->name;
@ -67,6 +78,7 @@ class FieldEditor extends FormField {
} }
} }
} }
return $fields; return $fields;
} }
} }
@ -161,10 +173,6 @@ class FieldEditor extends FormField {
if($record->hasMethod('customFormSave')) { if($record->hasMethod('customFormSave')) {
$record->customFormSave($_REQUEST[$name], $record); $record->customFormSave($_REQUEST[$name], $record);
} }
if($record->hasMethod('processNewFormFields')) {
$record->processNewFormFields();
}
} }
/** /**
@ -230,28 +238,4 @@ class FieldEditor extends FormField {
} }
return false; return false;
} }
function setHasFormOptions($bool){
$this->hasFormOptions = $bool;
}
function hasFormOptions(){
return $this->hasFormOptions;
}
function FormOptions() {
if($this->hasFormOptions()){
if($this->form->getRecord()->hasMethod('customFormActions')) {
$newFields = $this->form->getRecord()->customFormActions($this->readonly);
foreach($newFields as $newField) {
$newField->setName("{$this->name}[{$newField->Name()}]" );
}
if($this->readonly) {
$newFields = $newFields->makeReadonly();
}
return $newFields;
}
}
}
} }

View File

@ -6,6 +6,7 @@
*/ */
class SubmittedForm extends DataObject { class SubmittedForm extends DataObject {
static $has_one = array( static $has_one = array(
"SubmittedBy" => "Member", "SubmittedBy" => "Member",
"Parent" => "UserDefinedForm", "Parent" => "UserDefinedForm",

View File

@ -69,93 +69,7 @@ class SubmittedFormReportField extends FormField {
// Get the UserDefinedForm to export data from the URL // Get the UserDefinedForm to export data from the URL
$SQL_ID = (isset($_REQUEST['id'])) ? Convert::raw2sql($_REQUEST['id']) : false; $SQL_ID = (isset($_REQUEST['id'])) ? Convert::raw2sql($_REQUEST['id']) : false;
if($SQL_ID) { return $this->generateExport($SQL_ID);
$udf = DataObject::get_by_id("UserDefinedForm", $SQL_ID);
if($udf) {
$submissions = $udf->Submissions();
if($submissions && $submissions->Count() > 0) {
// Get all the submission IDs (so we know what names/titles to get - helps for sites with many UDF's)
$inClause = array();
foreach($submissions as $submission) {
$inClause[] = $submission->ID;
}
// Get the CSV header rows from the database
$tmp = DB::query("SELECT DISTINCT \"SubmittedFormField\".\"ID\", \"Name\", \"Title\"
FROM \"SubmittedFormField\"
LEFT JOIN \"SubmittedForm\" ON \"SubmittedForm\".\"ID\" = \"SubmittedFormField\".\"ParentID\"
WHERE \"SubmittedFormField\".\"ParentID\" IN (" . implode(',', $inClause) . ")
GROUP BY \"Name\"
ORDER BY \"SubmittedFormField\".\"ID\"");
// Sort the Names and Titles from the database query into separate keyed arrays
foreach($tmp as $array) {
$csvHeaderNames[] = $array['Name'];
$csvHeaderTitle[] = $array['Title'];
}
// For every submission...
$i = 0;
foreach($submissions as $submission) {
// Get the rows for this submission (One row = one form field)
$dataRow = $submission->FieldValues();
$rows[$i] = array();
// For every row/field, get all the columns
foreach($dataRow as $column) {
// If the Name of this field is in the $csvHeaderNames array, get an array of all the places it exists
if($index = array_keys($csvHeaderNames, $column->Name)) {
if(is_array($index)) {
// Set the final output array for each index that we want to insert this value into
foreach($index as $idx) {
$rows[$i][$idx] = $column->Value;
}
$rows[$i]['Submitted'] = $submission->Created;
}
}
}
$i++;
}
// CSV header row
$csvData = '"' . implode('","', $csvHeaderTitle) . '"' . ',"Submitted"'."\n";
// For every row of data (one form submission = one row)
foreach($rows as $row) {
// Loop over all the names we can use
for($i=0;$i<count($csvHeaderNames);$i++) {
if(!isset($row[$i]) || !$row[$i]) $csvData .= '"",'; // If there is no data for this column, output it as blank instead
else {
$tmp = str_replace('"', '""', $row[$i]);
$csvData .= '"' . $tmp . '",';
}
}
// Start a new row for each submission (re-check we have 'Submitted' in this entry)
if(isset($row['Submitted'])) $csvData .= '"'.$row['Submitted'].'"'."\n";
else $csvData .= '\n';
}
} else {
user_error("No submissions to export.", E_USER_ERROR);
}
if(class_exists('SS_HTTPRequest')) {
SS_HTTPRequest::send_file($csvData, $fileName)->output();
} else {
HTTPRequest::send_file($csvData, $fileName)->output();
}
} else {
user_error("'$SQL_ID' is a valid type, but we can't find a UserDefinedForm in the database that matches the ID.", E_USER_ERROR);
}
} else {
user_error("'$SQL_ID' is not a valid UserDefinedForm ID.", E_USER_ERROR);
}
} }
/** /**

View File

@ -6,6 +6,10 @@
(function($) { (function($) {
$(document).ready(function() { $(document).ready(function() {
/**
* Update the Field sortable
*/
update_sortable();
/** /**
* Update the sortable properties of the form as a function * Update the sortable properties of the form as a function
@ -75,11 +79,6 @@
/*-------------------- FIELD EDITOR ----------------------- */ /*-------------------- FIELD EDITOR ----------------------- */
/**
* Update the Field sortable
*/
update_sortable();
/** /**
* Create a new instance of a field in the current form * Create a new instance of a field in the current form
* area. the type information should all be on this object * area. the type information should all be on this object

View File

@ -1,10 +1,10 @@
<!-- JS Relys on EditableFormField as a class - and the 3 ids in this order - do not change --> <!-- JS Relys on EditableFormField as a class - and the 3 ids in this order - do not change -->
<li class="$ClassName EditableFormField" id="$Name.ATT EditableItem_$Pos $Name"> <li class="$ClassName EditableFormField" id="$Name.ATT EditableItem_$Pos $Name">
<div class="fieldInfo"> <div class="fieldInfo">
<% if isReadonly %> <% if canEdit %>
<img class="fieldHandler" src="sapphire/images/drag_readonly.gif" alt="<% _t('LOCKED', 'These fields cannot be modified') %>" />
<% else %>
<img class="fieldHandler" src="sapphire/images/drag.gif" alt="<% _t('DRAG', 'Drag to rearrange order of fields') %>" /> <img class="fieldHandler" src="sapphire/images/drag.gif" alt="<% _t('DRAG', 'Drag to rearrange order of fields') %>" />
<% else %>
<img class="fieldHandler" src="sapphire/images/drag_readonly.gif" alt="<% _t('LOCKED', 'These fields cannot be modified') %>" />
<% end_if %> <% end_if %>
<img class="icon" src="$Icon" alt="$ClassName" title="$singular_name" /> <img class="icon" src="$Icon" alt="$ClassName" title="$singular_name" />

View File

@ -3,9 +3,9 @@
<input type="text" name="{$FieldName}[Title]" value="$Title" /> <input type="text" name="{$FieldName}[Title]" value="$Title" />
<input type="hidden" class="sortOptionHidden hidden" name="{$FieldName}[Sort]" value="$Sort" /> <input type="hidden" class="sortOptionHidden hidden" name="{$FieldName}[Sort]" value="$Sort" />
<% if isReadonly %> <% if canEdit %>
<img src="cms/images/locked.gif" alt="<% _t('LOCKED', 'These fields cannot be modified') %>" />
<% else %>
<a href="$ID" class="deleteOption"><img src="cms/images/delete.gif" alt="<% _t('DELETE', 'Remove this option') %>" /></a> <a href="$ID" class="deleteOption"><img src="cms/images/delete.gif" alt="<% _t('DELETE', 'Remove this option') %>" /></a>
<% else %>
<img src="cms/images/locked.gif" alt="<% _t('LOCKED', 'These fields cannot be modified') %>" />
<% end_if %> <% end_if %>
</li> </li>

View File

@ -1,4 +1,8 @@
<div class="FieldEditor <% if isReadonly %>readonly<% end_if %>" id="Fields"> <% require css(userforms/css/FieldEditor.css) %>
<% require javascript(sapphire/thirdparty/jquery-ui/jquery-ui-1.8rc3.custom.js) %>
<% require javascript(userforms/javascript/UserForm.js) %>
<div class="FieldEditor <% if canEdit %><% else %>readonly<% end_if %>" id="Fields">
<div class="FieldListHold"> <div class="FieldListHold">
<ul class="FieldList" id="Fields_fields"> <ul class="FieldList" id="Fields_fields">
@ -8,12 +12,19 @@
</ul> </ul>
</div> </div>
<% include AddField %> <% if canEdit %>
<div class="MenuHolder">
<h2><% _t('ADD', 'Add') %></h2>
<div class="FormOptions"> <select name="AddUserFormField" id="AddUserFormField">
<h3>Form Options</h3> <option value=""><% _t('SELECTAFIELD', 'Select a Field') %></option>
<% control FormOptions %>
$FieldHolder <% control CreatableFields %>
<option value="$ClassName">$Title</option>
<% end_control %> <% end_control %>
</select>
<input type="submit" class="action" value="<% _t('ADD', 'Add') %>" />
</div> </div>
<% end_if %>
</div> </div>

View File

@ -1,14 +0,0 @@
<% if canEdit %>
<div class="MenuHolder">
<h2><% _t('ADD', 'Add') %></h2>
<select name="AddUserFormField" id="AddUserFormField">
<option value=""><% _t('SELECTAFIELD', 'Select a Field') %></option>
<% control CreatableFields %>
<option value="$ClassName">$Title</option>
<% end_control %>
</select>
<input type="submit" class="action" value="<% _t('ADD', 'Add') %>" />
</div>
<% end_if %>

View File

@ -0,0 +1,45 @@
<?php
/**
* @package userforms
*/
class EditableFormFieldTest extends FunctionalTest {
static $fixture_file = 'userforms/tests/EditableFormFields.yml';
function testEditableDropdownField() {
$dropdown = $this->objFromFixture('EditableDropdown', 'basic-dropdown');
$option1 = $this->objFromFixture('EditableOption', 'option-1');
$option2 = $this->objFromFixture('EditableOption', 'option-2');
$dropdown->Options()->add($option1);
$dropdown->Options()->add($option2);
$field = $dropdown->getFormField();
$this->assertThat($field, $this->isInstanceOf('DropdownField'));
$values = $field->getSource();
$this->assertEquals(array('Option 1' => 'Option 1', 'Option 2' => 'Option 2'), $values);
}
function testEditableRadioField() {
$radio = $this->objFromFixture('EditableRadioField', 'radio-field');
$option1 = $this->objFromFixture('EditableOption', 'option-1');
$option2 = $this->objFromFixture('EditableOption', 'option-2');
$radio->Options()->add($option1);
$radio->Options()->add($option2);
$field = $radio->getFormField();
$this->assertThat($field, $this->isInstanceOf('OptionsetField'));
$values = $field->getSource();
$this->assertEquals(array('Option 1' => 'Option 1', 'Option 2' => 'Option 2'), $values);
}
}

View File

@ -0,0 +1,62 @@
EditableOption:
option-1:
Name: Option1
Title: Option 1
Sort: 1
option-2:
Name: Option2
Title: Option 2
Sort: 2
department-1:
Name: dept1
Title: sales@example.com
department-2:
Name: dept2
Title: accounts@example.com
EditableTextField:
basic-text:
Name: basic-text-name
Title: Basic Text Field
required-text:
Name: required-text-field
Title: Required Text Field
CustomErrorMessage: Custom Error Message
RightTitle: Right Title
Required: true
EditableRadioField:
radio-field:
Name: radio-option
Title: Radio Option
EditableDropdown:
basic-dropdown:
Name: basic-dropdown
Title: Basic Dropdown Field
Options: =>EditableOption.option-1, =>EditableOption.option-2
department-dropdown:
Name: department
Title: Department
Options: =>EditableOption.department-1, =>EditableOption.department-2
EditableCheckbox:
checkbox-1:
Name: checkbox-1
Title: Checkbox 1
EditableEmailField:
email-field:
Name: email-field
Title: Email
EditableCheckboxGroupField:
checkbox-group:
Name: check-box-group
Title: Check box group
Options: =>EditableOption.option-1, =>EditableOption.option-2

37
tests/FieldEditorTest.php Normal file
View File

@ -0,0 +1,37 @@
<?php
/**
* Tests covering the form editor / builder and
* some of the user interface
*
* @package userforms
*/
class FieldEditorTest extends FunctionalTest {
static $fixture_file = 'userforms/tests/UserDefinedFormTest.yml';
function testPerformReadonlyTransformation() {
}
function testSaveInto() {
}
function testAddField() {
}
function testAddOptionField() {
}
function testCreatableFields() {
}
}
class FieldEditorTest_Controller extends Controller {
}

View File

@ -0,0 +1,68 @@
<?php
class SubmittedFormTest extends FunctionalTest {
static $fixture_file = 'userforms/tests/SubmittedFormTest.yml';
function testReportSubmissions() {
}
function testCSVExport() {
}
function testdeletesubmission() {
}
function testdeletesubmissions() {
}
function testOnBeforeDeleteOfForm() {
$field = $this->objFromFixture('SubmittedFormField', 'submitted-form-field-1');
$form = $field->Parent();
$this->assertEquals($form->FieldValues()->Count(), 2);
$form->delete();
$fields = DataObject::get('SubmittedFormField', "ParentID = '$form->ID'");
$this->assertNull($fields);
}
function testGetFormattedValue() {
$field = $this->objFromFixture('SubmittedFormField', 'submitted-form-field-1');
$this->assertEquals('1', $field->getFormattedValue());
$textarea = $this->objFromFixture('SubmittedFormField', 'submitted-textarea-1');
$text = "I am here testing<br />\nTesting until I cannot<br />\nI love my testing";
$this->assertEquals($text, $textarea->getFormattedValue());
}
function testFileGetLink() {
$field = $this->objFromFixture('SubmittedFileField', 'submitted-file-1');
// @todo add checks for if no file can be downloaded
$this->assertContains('my-file.jpg', $field->getLink());
}
function testFileGetFormattedValue() {
$field = $this->objFromFixture('SubmittedFileField', 'submitted-file-1');
// @todo add checks for if no file can be downloaded
$this->assertContains('Download File', $field->getFormattedValue());
}
}
class SubmittedFormTest_Controller extends Controller {
function ReportField() {
return new Form($this, 'ReportField', new FieldSet(new SubmittedFormReportField('Report'), new FieldSet()));
}
}

View File

@ -0,0 +1,43 @@
UserDefinedForm:
form-page:
Title: Form
form-page-2:
Title: Second Form
File:
uploaded-file:
Name: My File
Filename: my-file.jpg
SubmittedForm:
submitted-form-1:
Parent: =>UserDefinedForm.form-page
submitted-form-2:
Parent: =>UserDefinedForm.form-page-2
SubmittedFormField:
submitted-form-field-1:
Parent: =>SubmittedForm.submitted-form-1
Name: field-1
Title: Field 1
Value: 1
submitted-textarea-1:
Parent: =>SubmittedForm.submitted-form-2
Name: field 2
Title: Field 2
Value: |
I am here testing
Testing until I cannot
I love my testing
SubmittedFileField:
submitted-file-1:
Name: File Field
Title: File
Parent: =>SubmittedForm.submitted-form-1
UploadedFile: =>File.uploaded-file

View File

@ -0,0 +1,196 @@
<?php
/**
* @package userforms
*/
class UserDefinedFormControllerTest extends FunctionalTest {
static $fixture_file = 'userforms/tests/UserDefinedFormTest.yml';
function testProcess() {
$form = $this->setupFormFrontend();
$controller = new UserDefinedForm_Controller($form);
$this->autoFollowRedirection = false;
$this->clearEmails();
// load the form
$this->get($form->URLSegment);
$response = $this->submitForm('Form_Form', null, array('basic-text-name' => 'Basic Value'));
// should have a submitted form field now
$submitted = DataObject::get('SubmittedFormField', "\"Name\" = 'basic-text-name'");
$this->assertDOSAllMatch(array('Name' => 'basic-text-name', 'Value' => 'Basic Value', 'Title' => 'Basic Text Field'), $submitted);
// check emails
$this->assertEmailSent('test@example.com', 'no-reply@example.com', 'Email Subject');
$email = $this->findEmail('test@example.com', 'no-reply@example.com', 'Email Subject');
// assert that the email has the field title and the value html email
$parser = new CSSContentParser($email['content']);
$title = $parser->getBySelector('strong');
$this->assertEquals('Basic Text Field', (string) $title[0], 'Email contains the field name');
$value = $parser->getBySelector('dd');
$this->assertEquals('Basic Value', (string) $value[0], 'Email contains the value');
// no html
$this->assertEmailSent('nohtml@example.com', 'no-reply@example.com', 'Email Subject');
$nohtml = $this->findEmail('nohtml@example.com', 'no-reply@example.com', 'Email Subject');
$this->assertContains('Basic Text Field - Basic Value', $nohtml['content'], 'Email contains no html');
// no data
$this->assertEmailSent('nodata@example.com', 'no-reply@example.com', 'Email Subject');
$nodata = $this->findEmail('nodata@example.com', 'no-reply@example.com', 'Email Subject');
$parser = new CSSContentParser($nodata['content']);
$list = $parser->getBySelector('dl');
$this->assertFalse(isset($list[0]), 'Email contains no fields');
// check to see if the user was redirected (301)
$this->assertEquals($response->getStatusCode(), 302);
$this->assertStringEndsWith('finished', $response->getHeader('Location'));
}
function testFinished() {
$form = $this->setupFormFrontend();
$response = $this->get($form->URLSegment.'/finished');
$this->assertContains($form->OnCompleteMessage ,$response->getBody());
}
function testForm() {
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$controller = new UserDefinedForm_Controller($form);
// test form
$this->assertEquals($controller->Form()->Name(), 'Form', 'The form is referenced as Form');
$this->assertEquals($controller->Form()->Fields()->Count(), 2);
$this->assertEquals($controller->Form()->Actions()->Count(), 1);
$this->assertEquals(count($controller->Form()->getValidator()->getRequired()), 0);
$requiredForm = $this->objFromFixture('UserDefinedForm', 'validation-form');
$controller = new UserDefinedForm_Controller($requiredForm);
$this->assertEquals($controller->Form()->Fields()->Count(), 2);
$this->assertEquals($controller->Form()->Actions()->Count(), 1);
$this->assertEquals(count($controller->Form()->getValidator()->getRequired()), 1);
}
function testgetFormFields() {
// generating the fieldset of fields
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$controller = new UserDefinedForm_Controller($form);
$fields = $controller->getFormFields();
$this->assertEquals($fields->Count(), 1);
// custom error message on a form field
$requiredForm = $this->objFromFixture('UserDefinedForm', 'validation-form');
$controller = new UserDefinedForm_Controller($requiredForm);
UserDefinedForm::$required_identifier = "*";
$fields = $controller->getFormFields();
$this->assertEquals($fields->First()->getCustomValidationMessage(), 'Custom Error Message');
}
function testGetFormActions() {
// generating the fieldset of actions
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$controller = new UserDefinedForm_Controller($form);
$actions = $controller->getFormActions();
// by default will have 1 submit button which links to process
$expected = new FieldSet(new FormAction('process', 'Submit'));
$this->assertEquals($actions, $expected);
// the custom popup should have a reset button and a custom text
$custom = $this->objFromFixture('UserDefinedForm', 'form-with-reset-and-custom-action');
$controller = new UserDefinedForm_Controller($custom);
$actions = $controller->getFormActions();
$expected = new FieldSet(new FormAction('process', 'Custom Button'));
$expected->push(new ResetFormAction("clearForm"));
$this->assertEquals($actions, $expected);
}
function testArrayToJson() {
$array = array('1' => 'one', '2' => 'two');
$string = "{\n1:\"one\", 2:\"two\"\n}\n";
$this->assertEquals(UserDefinedForm_Controller::array2json($array), $string);
}
function testRenderingIntoFormTemplate() {
$form = $this->setupFormFrontend();
$form->Content = 'This is some content without a form nested between it';
$form->doPublish();
$controller = new UserDefinedForm_Controller($form);
// check to see if $Form is replaced to inside the content
$index = new ArrayData($controller->index());
$parser = new CSSContentParser($index->renderWith(array('UserDefinedFormControllerTest')));
$this->checkTemplateIsCorrect($parser);
}
function testRenderingIntoTemplateWithSubstringReplacement() {
$form = $this->setupFormFrontend();
$controller = new UserDefinedForm_Controller($form);
// check to see if $Form is replaced to inside the content
$index = new ArrayData($controller->index());
$parser = new CSSContentParser($index->renderWith(array('UserDefinedFormControllerTest')));
$this->checkTemplateIsCorrect($parser);
}
function setupFormFrontend() {
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$this->logInWithPermission('ADMIN');
$form->doPublish();
$member = Member::currentUser();
$member->logOut();
return $form;
}
function checkTemplateIsCorrect($parser) {
$this->assertArrayHasKey(0, $parser->getBySelector('form#Form_Form'));
// check for the input
$this->assertArrayHasKey(0, $parser->getBySelector('input.text'));
// check for the label and the text
$label = $parser->getBySelector('label.left');
$this->assertArrayHasKey(0, $label);
$this->assertEquals((string) $label[0][0], "Basic Text Field", "Label contains correct field name");
// check for the action
$action = $parser->getBySelector('input.action');
$this->assertArrayHasKey(0, $action);
$this->assertEquals((string) $action[0]['value'], "Submit", "Submit button has default text");
}
}

View File

@ -1,102 +0,0 @@
<?php
/**
* Tests covering the form editor / builder and
* some of the user interface
*
* @package userforms
*/
class UserDefinedFormEditorTest extends FunctionalTest {
protected $form;
function setUp() {
parent::setUp();
$this->logInWithPermission('ADMIN');
$this->form = new UserDefinedForm();
$this->form->write();
}
function testPublishingNormalField() {
$id = $this->form->ID;
// test a normal field
$field = new EditableFormField();
$field->write();
$this->form->Fields()->add($field);
// upon adding it, it shouldn't be on the live site
$live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $id");
$this->assertFalse($live);
// upon publishing the field should exist
$this->form->doPublish();
$live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $id");
$this->assertEquals($live->Fields()->Count(), 1);
}
function testPublishingMultipleOptions() {
$id = $this->form->ID;
$this->form->Fields()->removeAll();
// test a editable option field
$dropdown = new EditableDropdown();
$dropdown->write();
$checkbox = new EditableCheckboxGroupField();
$checkbox->write();
$option = new EditableOption();
$option->write();
$option2 = new EditableOption();
$option2->write();
$dropdown->Options()->add($option);
$checkbox->Options()->add($option2);
$this->form->Fields()->add($dropdown);
$this->form->Fields()->add($checkbox);
// upon adding it, it shouldn't be on the live site
$live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $id");
$this->assertFalse($live);
// and when published it should exist and the option
$this->form->doPublish();
$live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $id");
$this->assertEquals($live->Fields()->Count(), 2);
// check they have options attached
foreach($live->Fields() as $field) {
$this->assertEquals($field->Options()->Count(), 1);
}
}
function testUnpublishing() {
$id = $this->form->ID;
$this->form->Fields()->removeAll();
$this->form->Fields()->add(new EditableFormField());
$this->form->doUnPublish();
$live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $id");
$stage = Versioned::get_one_by_stage("UserDefinedForm", "Stage", "\"UserDefinedForm\".\"ID\" = $id");
$this->assertEquals($live, false);
$this->assertEquals($stage->Fields()->Count(), 1);
}
function testDuplicatingPage() {
$this->form->Fields()->add(new EditableFormField());
$form_copy = $this->form->duplicate();
$this->assertEquals($this->form->Fields()->Count(), $form_copy->Fields()->Count());
}
function tearDown() {
$this->form->delete();
parent::tearDown();
}
}

View File

@ -1,28 +0,0 @@
<?php
/**
* Various tests for the user defined forms.
* Does not test the user interface in the admin.
*
* @todo Add more comprehensive tests
* @package userforms
*/
class UserDefinedFormFieldTest extends SapphireTest {
/**
* Basic Test creating all the editable form fields
*/
function testCreatingAllFields() {
$fields = ClassInfo::subclassesFor('EditableFormField');
foreach($fields as $field) {
$object = new $field();
$object->Name = "$field";
$object->Title = "$field";
$object->write();
$this->assertEquals($field, $object->Name);
$object->delete();
}
}
}

View File

@ -0,0 +1,221 @@
<?php
/**
* @package userforms
*/
class UserDefinedFormTest extends FunctionalTest {
static $fixture_file = 'userforms/tests/UserDefinedFormTest.yml';
function testRollbackToVersion() {
// @todo rolling back functionality (eg fields) is not supported yet
$this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$form->SubmitButtonText = 'Button Text';
$form->write();
$form->doPublish();
$origVersion = $form->Version;
$form->SubmitButtonText = 'Updated Button Text';
$form->write();
$form->doPublish();
// check published site
$updated = Versioned::get_one_by_stage("UserDefinedForm", "Stage", "\"UserDefinedForm\".\"ID\" = $form->ID");
$this->assertEquals($updated->SubmitButtonText, 'Updated Button Text');
$form->doRollbackTo($origVersion);
$orignal = Versioned::get_one_by_stage("UserDefinedForm", "Stage", "\"UserDefinedForm\".\"ID\" = $form->ID");
$this->assertEquals($orignal->SubmitButtonText, 'Button Text');
}
function testGetCMSFields() {
// ensure all the tabs are present.
// @todo a common bug with this is translations messing up the tabs.
// @todo only logic we should check for is that the tablelistfield filter
$this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$fields = $form->getCMSFields();
$this->assertTrue($fields->dataFieldByName('Fields') !== null);
$this->assertTrue($fields->dataFieldByName('EmailRecipients') != null);
$this->assertTrue($fields->dataFieldByName('Reports') != null);
$this->assertTrue($fields->dataFieldByName('OnCompleteMessage') != null);
}
function testEmailRecipientPopup() {
$this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$popup = new UserDefinedForm_EmailRecipient();
$fields = $popup->getCMSFields_forPopup();
$this->assertTrue($fields->dataFieldByName('EmailSubject') !== null);
$this->assertTrue($fields->dataFieldByName('EmailFrom') !== null);
$this->assertTrue($fields->dataFieldByName('EmailAddress') !== null);
$this->assertTrue($fields->dataFieldByName('HideFormData') !== null);
$this->assertTrue($fields->dataFieldByName('SendPlain') !== null);
$this->assertTrue($fields->dataFieldByName('EmailBody') !== null);
// add an email field, it should now add a or from X address picker
$email = $this->objFromFixture('EditableEmailField','email-field');
$form->Fields()->add($email);
$popup->Form = $form;
$popup->write();
$fields = $popup->getCMSFields_forPopup();
$this->assertThat($fields->fieldByName('SendEmailToFieldID'), $this->isInstanceOf('DropdownField'));
// if the front end has checkboxs or dropdown they can select from that can also be used to send things
$dropdown = $this->objFromFixture('EditableDropdown', 'department-dropdown');
$form->Fields()->add($dropdown);
$fields = $popup->getCMSFields_forPopup();
$this->assertTrue($fields->dataFieldByName('SendEmailToFieldID') !== null);
$popup->delete();
}
function testPublishing() {
$this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$form->write();
$form->doPublish();
$live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID");
$this->assertNotNull($live);
$this->assertEquals($live->Fields()->Count(), 1);
$dropdown = $this->objFromFixture('EditableDropdown', 'basic-dropdown');
$form->Fields()->add($dropdown);
$stage = Versioned::get_one_by_stage("UserDefinedForm", "Stage", "\"UserDefinedForm\".\"ID\" = $form->ID");
$this->assertEquals($stage->Fields()->Count(), 2);
// should not have published the dropdown
$liveDropdown = Versioned::get_one_by_stage("EditableFormField", "Live", "\"EditableFormField_Live\".\"ID\" = $dropdown->ID");
$this->assertFalse($liveDropdown);
// when publishing it should have added it
$form->doPublish();
$live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID");
$this->assertEquals($live->Fields()->Count(), 2);
// edit the title
$text = $form->Fields()->First();
$text->Title = 'Edited title';
$text->write();
$liveText = Versioned::get_one_by_stage("EditableFormField", "Live", "\"EditableFormField_Live\".\"ID\" = $text->ID");
$this->assertFalse($liveText->Title == $text->Title);
$form->doPublish();
$liveText = Versioned::get_one_by_stage("EditableFormField", "Live", "\"EditableFormField_Live\".\"ID\" = $text->ID");
$this->assertTrue($liveText->Title == $text->Title);
}
function testUnpublishing() {
$this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$form->write();
$form->doPublish();
// assert that it exists and has a field
$live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID");
$this->assertTrue(isset($live));
$this->assertEquals(DB::query("SELECT COUNT(*) FROM \"EditableFormField_Live\"")->value(), 1);
// unpublish
$form->doUnpublish();
$this->assertFalse(Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID"));
$this->assertEquals(DB::query("SELECT COUNT(*) FROM \"EditableFormField_Live\"")->value(), 0);
}
function testDoRevertToLive() {
$this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$form->SubmitButtonText = 'Button Text';
$form->doPublish();
$text = $form->Fields()->First();
$form->SubmitButtonText = 'Edited Button Text';
$form->write();
$text->Title = 'Edited title';
$text->write();
// check that the published version is not updated
$liveText = Versioned::get_one_by_stage("EditableFormField", "Live", "\"EditableFormField_Live\".\"ID\" = $text->ID");
$revertTo = $liveText->Title;
$this->assertFalse($revertTo == $text->Title);
// revert back to the live data
$form->doRevertToLive();
$check = Versioned::get_one_by_stage("EditableFormField", "Stage", "\"EditableFormField\".\"ID\" = $text->ID");
$this->assertEquals($check->Title, $revertTo);
// check the edited buttoned
$liveForm = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID");
$revertedForm = Versioned::get_one_by_stage("UserDefinedForm", "Stage", "\"UserDefinedForm\".\"ID\" = $form->ID");
$this->assertEquals($liveForm->SubmitButtonText, $revertedForm->SubmitButtonText);
}
function testDuplicatingForm() {
$this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$duplicate = $form->duplicate();
$this->assertEquals($form->Fields()->Count(), $duplicate->Fields()->Count());
$this->assertEquals($form->EmailRecipients()->Count(), $form->EmailRecipients()->Count());
// can't compare object since the dates/ids change
$this->assertEquals($form->Fields()->First()->Title, $duplicate->Fields()->First()->Title);
}
/**
* @todo once getIsModifiedOnStage is implemented will need to implement this
*/
function testGetIsModifiedOnStage() {
$this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$this->assertTrue($form->getIsModifiedOnStage());
}
function testFormOptions() {
$this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$fields = $form->getFormOptions();
$submit = $fields->fieldByName('SubmitButtonText');
$reset = $fields->fieldByName('ShowClearButton');
$this->assertEquals($submit->Title(), 'Text on submit button:');
$this->assertEquals($reset->Title(), 'Show Clear Form Button');
}
}

View File

@ -0,0 +1,87 @@
EditableOption:
option-1:
Name: Option1
Title: Option 1
Sort: 1
option-2:
Name: Option2
Title: Option 2
Sort: 2
department-1:
Name: dept1
Title: sales@example.com
department-2:
Name: dept2
Title: accounts@example.com
UserDefinedForm_EmailRecipient:
recipient-1:
EmailAddress: test@example.com
EmailSubject: Email Subject
EmailFrom: no-reply@example.com
no-html:
EmailAddress: nohtml@example.com
EmailSubject: Email Subject
EmailFrom: no-reply@example.com
SendPlain: true
no-data:
EmailAddress: nodata@example.com
EmailSubject: Email Subject
EmailFrom: no-reply@example.com
HideFormData: true
EditableTextField:
basic-text:
Name: basic-text-name
Title: Basic Text Field
required-text:
Name: required-text-field
Title: Required Text Field
CustomErrorMessage: Custom Error Message
RightTitle: Right Title
Required: true
EditableDropdown:
basic-dropdown:
Name: basic-dropdown
Title: Basic Dropdown Field
Options: =>EditableOption.option-1, =>EditableOption.option-2
department-dropdown:
Name: department
Title: Department
Options: =>EditableOption.department-1, =>EditableOption.department-2
EditableCheckbox:
checkbox-1:
Name: checkbox-1
Title: Checkbox 1
EditableEmailField:
email-field:
Name: email-field
Title: Email
UserDefinedForm:
basic-form-page:
Title: User Defined Form
Fields: =>EditableTextField.basic-text
EmailRecipients: =>UserDefinedForm_EmailRecipient.recipient-1, =>UserDefinedForm_EmailRecipient.no-html, =>UserDefinedForm_EmailRecipient.no-data
form-with-reset-and-custom-action:
Title: Form with Reset Action
SubmitButtonText: Custom Button
ShowClearButton: true
validation-form:
Title: Validation Form
Fields: =>EditableTextField.required-text

View File

@ -0,0 +1,5 @@
<!DOCTYPE html>
<html>
$Content
$Form
</html>