API new form editor

This commit is contained in:
David Craig 2015-07-24 14:37:48 +12:00 committed by Damian Mooyman
parent 8f92d75975
commit 51864a6308
51 changed files with 2367 additions and 3014 deletions

View File

@ -1,6 +1,9 @@
# See https://github.com/silverstripe-labs/silverstripe-travis-support for setup details
language: php
language: php
sudo: false
php:
- 5.4

View File

@ -3,3 +3,5 @@
if(!defined('USERFORMS_DIR')) {
define('USERFORMS_DIR', basename(__DIR__));
}
Deprecation::notification_version('3.0', 'userforms');

View File

@ -1,9 +0,0 @@
---
name: userforms
---
LeftAndMain:
extra_requirements_javascript:
- userforms/javascript/UserForm.js
extra_requirements_css:
- userforms/css/FieldEditor.css

View File

@ -0,0 +1,186 @@
<?php
/**
* @package userforms
*/
class UserFormFieldEditorExtension extends DataExtension {
/**
* @var array
*/
private static $has_many = array(
'Fields' => 'EditableFormField'
);
/**
* Adds the field editor to the page.
*
* @return FieldList
*/
public function updateCMSFields(FieldList $fields) {
$fieldEditor = $this->getFieldEditorGrid();
$fields->insertAfter(new Tab('FormFields', _t('UserFormFieldEditorExtension.FORMFIELDS', 'Form Fields')), 'Main');
$fields->addFieldToTab('Root.FormFields', $fieldEditor);
return $fields;
}
/**
* Gets the field editor, for adding and removing EditableFormFields.
*
* @return GridField
*/
public function getFieldEditorGrid() {
$fields = $this->owner->Fields();
$editableColumns = new GridFieldEditableColumns();
$editableColumns->setDisplayFields(array(
'ClassName' => function($record, $column, $grid) {
return DropdownField::create($column, '', $this->getEditableFieldClasses());
},
'Title' => function($record, $column, $grid) {
return TextField::create($column, ' ')
->setAttribute('placeholder', _t('UserDefinedForm.TITLE', 'Title'));
}
));
$fieldEditor = GridField::create(
'Fields',
_t('UserDefinedForm.FIELDS', 'Fields'),
$fields,
GridFieldConfig::create()
->addComponents(
$editableColumns,
new GridFieldButtonRow(),
new GridFieldAddNewInlineButton(),
new GridFieldEditButton(),
new GridFieldDeleteAction(),
new GridFieldToolbarHeader(),
new GridFieldOrderableRows('Sort'),
new GridState_Component(),
new GridFieldDetailForm()
)
);
return $fieldEditor;
}
/**
* @return array
*/
public function getEditableFieldClasses() {
$classes = ClassInfo::getValidSubClasses('EditableFormField');
// Remove classes we don't want to display in the dropdown.
$classes = array_diff($classes, array(
'EditableFormField',
'EditableMultipleOptionField'
));
$editableFieldClasses = array();
foreach ($classes as $key => $className) {
$singleton = singleton($className);
if(!$singleton->canCreate()) {
continue;
}
$editableFieldClasses[$className] = $singleton->i18n_singular_name();
}
return $editableFieldClasses;
}
/**
* @see SiteTree::doPublish
* @param Page $original
*
* @return void
*/
public function onAfterPublish($original) {
// Remove fields on the live table which could have been orphaned.
$live = Versioned::get_by_stage("EditableFormField", "Live")
->filter('ParentID', $original->ID);
if($live) {
foreach($live as $field) {
$field->doDeleteFromStage('Live');
}
}
foreach($this->owner->Fields() as $field) {
$field->doPublish('Stage', 'Live');
}
}
/**
* @see SiteTree::doUnpublish
* @param Page $page
*
* @return void
*/
public function onAfterUnpublish($page) {
foreach($page->Fields() as $field) {
$field->doDeleteFromStage('Live');
}
}
/**
* @see SiteTree::duplicate
* @param DataObject $newPage
*
* @return DataObject
*/
public function onAfterDuplicate($newPage) {
foreach($this->owner->Fields() as $field) {
$newField = $field->duplicate(false);
$newField->ParentID = $newPage->ID;
$newField->ParentClass = $newPage->ClassName;
$newField->Version = 0;
$newField->write();
foreach ($field->DisplayRules() as $customRule) {
$newRule = $customRule->duplicate(false);
$newRule->ParentID = $newField->ID;
$newRule->Version = 0;
$newRule->write();
}
}
return $newPage;
}
/**
* @see SiteTree::getIsModifiedOnStage
* @param boolean $isModified
*
* @return boolean
*/
public function getIsModifiedOnStage($isModified) {
if(!$isModified) {
foreach($this->owner->Fields() as $field) {
if($field->getIsModifiedOnStage()) {
$isModified = true;
break;
}
}
}
return $isModified;
}
/**
* @see SiteTree::doRevertToLive
* @param Page $page
*
* @return void
*/
public function onAfterRevertToLive($page) {
foreach($page->Fields() as $field) {
$field->publish('Live', 'Stage', false);
$field->writeWithoutVersion();
}
}
}

View File

@ -1,338 +0,0 @@
<?php
/**
* A form field allowing a user to customize and create form fields.
* for saving into a {@link UserDefinedForm}
*
* @package userforms
*/
class FieldEditor extends FormField {
private static $url_handlers = array(
'$Action!/$ID' => '$Action'
);
private static $allowed_actions = array(
'addfield',
'addoptionfield',
'handleField'
);
/**
* @param array $properties
*
* @return HTML
*/
public function FieldHolder($properties = array()) {
$add = Controller::join_links($this->Link('addfield'));
$this->setAttribute('data-add-url', '\''. $add.'\'');
return $this->renderWith("FieldEditor");
}
/**
* Returns whether a user can edit the form.
*
* @param Member $member
*
* @return boolean
*/
public function canEdit($member = null) {
if($this->readonly) {
return false;
}
return $this->form->getRecord()->canEdit();
}
/**
* Returns whether a user delete a field in the form. The
* {@link EditableFormField} instances check if they can delete themselves
* but this counts as an {@link self::canEdit()} function rather than a
* delete.
*
* @param Member $member
* @return boolean
*/
public function canDelete($member = null) {
if($this->readonly) return false;
return $this->form->getRecord()->canEdit();
}
/**
* Transform this form field to a readonly version.
*
* @return ViewableData_Customised
*/
public function performReadonlyTransformation() {
$clone = clone $this;
$clone->readonly = true;
$fields = $clone->Fields();
if($fields) {
foreach($fields as $field) {
$field->setReadonly();
}
}
return $clone->customise(array(
'Fields' => $fields
));
}
/**
* Return the fields.
*
* @return RelationList
*/
public function Fields() {
if($this->form && $this->form->getRecord() && $this->name) {
$relationName = $this->name;
$fields = $this->form->getRecord()->getComponents($relationName);
if($fields) {
foreach($fields as $field) {
if(!$this->canEdit() && is_a($field, 'FormField')) {
$fields->remove($field);
$fields->push($field->performReadonlyTransformation());
}
}
}
return $fields;
}
}
/**
* Return a {@link ArrayList} of all the addable fields to populate the add
* field menu.
*
* @return ArrayList
*/
public function CreatableFields() {
$fields = ClassInfo::subclassesFor('EditableFormField');
if($fields) {
array_shift($fields); // get rid of subclass 0
asort($fields); // get in order
$output = new ArrayList();
foreach($fields as $field => $title) {
// get the nice title and strip out field
$niceTitle = _t(
$field.'.SINGULARNAME',
$title
);
if($niceTitle && $field != "EditableMultipleOptionField") {
$output->push(new ArrayData(array(
'ClassName' => $field,
'Title' => "$niceTitle"
)));
}
}
return $output;
}
return false;
}
/**
* Handles saving the page. Needs to keep an eye on fields and options which
* have been removed / added
*
* @param DataObject $record
*/
public function saveInto(DataObjectInterface $record) {
$name = $this->name;
$fieldSet = $record->$name();
// store the field IDs and delete the missing fields
// alternatively, we could delete all the fields and re add them
$missingFields = array();
foreach($fieldSet as $existingField) {
$missingFields[$existingField->ID] = $existingField;
}
if(isset($_REQUEST[$name]) && is_array($_REQUEST[$name])) {
foreach($_REQUEST[$name] as $newEditableID => $newEditableData) {
if(!is_numeric($newEditableID)) continue;
// get it from the db
$editable = DataObject::get_by_id('EditableFormField', $newEditableID);
// if it exists in the db update it
if($editable) {
// remove it from the removed fields list
if(isset($missingFields[$editable->ID]) && isset($newEditableData) && is_array($newEditableData)) {
unset($missingFields[$editable->ID]);
}
// set form id
if($editable->ParentID == 0) {
$editable->ParentID = $record->ID;
}
// save data
$editable->populateFromPostData($newEditableData);
}
}
}
// remove the fields not saved
if($this->canEdit()) {
foreach($missingFields as $removedField) {
if(is_numeric($removedField->ID)) {
// check we can edit this
$removedField->delete();
}
}
}
}
/**
* Add a field to the field editor. Called via a ajax get.
*
* @return bool|html
*/
public function addfield() {
if(!SecurityToken::inst()->checkRequest($this->request)) {
return $this->httpError(400);
}
// get the last field in this form editor
$parentID = $this->form->getRecord()->ID;
if($parentID) {
$parentID = (int)$parentID;
$sqlQuery = new SQLQuery();
$sqlQuery = $sqlQuery
->setSelect("MAX(\"Sort\")")
->setFrom("\"EditableFormField\"")
->setWhere("\"ParentID\" = $parentID");
$sort = $sqlQuery->execute()->value() + 1;
$className = (isset($_REQUEST['Type'])) ? $_REQUEST['Type'] : '';
if(!$className) {
// A possible reason for the classname being blank is because we have execeded
// the number of input requests
// http://www.php.net/manual/en/info.configuration.php#ini.max-input-vars
$maxRequests = ini_get('max_input_vars');
$numRequests = count($_REQUEST, COUNT_RECURSIVE);
if ($numRequests > $maxRequests) {
$error = sprintf('You have exceded the maximum number %s of input requests',
"[$maxRequests]");
user_error($error, E_USER_WARNING);
}
user_error('Please select a field type to created', E_USER_WARNING);
}
if(is_subclass_of($className, "EditableFormField")) {
$field = new $className();
$field->ParentID = $this->form->getRecord()->ID;
$field->Name = $field->class . $field->ID;
$field->Sort = $sort;
$field->write();
return $field->EditSegment();
}
}
return false;
}
/**
* Return the html for a field option such as a
* dropdown field or a radio check box field
*
* @return bool|html
*/
public function addoptionfield() {
if(!SecurityToken::inst()->checkRequest($this->request)) {
return $this->httpError(400);
}
// passed via the ajax
$parent = (isset($_REQUEST['Parent'])) ? $_REQUEST['Parent'] : false;
// work out the sort by getting the sort of the last field in the form +1
if($parent) {
$sql_parent = (int)$parent;
$parentObj = EditableFormField::get()->byID($parent);
$optionClass = ($parentObj && $parentObj->exists()) ? $parentObj->getRelationClass('Options') : 'EditableOption';
$sqlQuery = new SQLQuery();
$sqlQuery = $sqlQuery
->setSelect("MAX(\"Sort\")")
->setFrom("\"EditableOption\"")
->setWhere("\"ParentID\" = $sql_parent");
$sort = $sqlQuery->execute()->value() + 1;
if($parent) {
$object = Injector::inst()->create($optionClass);
$object->write();
$object->ParentID = $parent;
$object->Sort = $sort;
$object->Name = 'option' . $object->ID;
$object->write();
return $object->EditSegment();
}
}
return false;
}
/**
* Pass sub {@link FormField} requests through the editor. For example,
* option fields need to be able to call themselves.
*
* @param SS_HTTPRequest
*/
public function handleField(SS_HTTPRequest $request) {
if(!SecurityToken::inst()->checkRequest($this->request)) {
return $this->httpError(400);
}
$fields = $this->Fields();
// extract the ID and option field name
preg_match(
'/Fields\[(?P<ID>\d+)\]\[CustomSettings\]\[(?P<Option>\w+)\]/',
$request->param('ID'), $matches
);
if(isset($matches['ID']) && isset($matches['Option'])) {
foreach($fields as $field) {
$formField = $field->getFormField();
if($matches['ID'] == $field->ID) {
if($field->canEdit()) {
// find the option to handle
$options = $field->getFieldConfiguration();
$optionField = $options->fieldByName($request->param('ID'));
if($optionField) {
return $optionField->handleRequest($request, $optionField);
} else {
return $this->httpError(404);
}
}
}
}
}
return $this->httpError(403);
}
}

View File

@ -4,17 +4,14 @@
* {@link TreeDropdownField} subclass for handling loading folders through the
* nested {@link FormField} instances of the {@link FieldEditor}
*
* @deprecated since version 4.0
* @package userforms
*/
class UserformsTreeDropdownField extends TreeDropdownField {
public function Link($action = null) {
$form = Controller::curr()->EditForm;
public function __construct($name, $title = null, $sourceObject = 'Group', $keyField = 'ID', $labelField = 'TreeTitle', $showSearch = true) {
parent::__construct($name, $title, $sourceObject, $keyField, $labelField, $showSearch);
return Controller::join_links(
$form->FormAction(), 'field/Fields/handleField/' . $this->name,
$action .
'?SecurityID='. $form->getSecurityToken()->getValue()
);
Deprecation::notice('4.0', __CLASS__ . " is deprecated");
}
}

158
code/forms/UserForm.php Normal file
View File

@ -0,0 +1,158 @@
<?php
/**
* @package userforms
*/
class UserForm extends Form {
/**
* @param Controller $controller
* @param string $name
*/
public function __construct(Controller $controller, $name = 'Form') {
$this->controller = $controller;
$this->setRedirectToFormOnValidationError(true);
parent::__construct(
$controller,
$name,
$this->getFormFields(),
$this->getFormActions(),
$this->getRequiredFields()
);
if($controller->DisableCsrfSecurityToken) {
$this->disableSecurityToken();
}
$data = Session::get("FormInfo.{$this->FormName()}.data");
if(is_array($data)) {
$this->loadDataFrom($data);
}
$this->extend('updateForm');
}
/**
* 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 FieldList
*/
public function getFormFields() {
$fields = new FieldList();
foreach($this->controller->Fields() as $editableField) {
// get the raw form field from the editable version
$field = $editableField->getFormField();
if(!$field) continue;
// set the error / formatting messages
$field->setCustomValidationMessage($editableField->getErrorMessage());
// set the right title on this field
if($right = $editableField->RightTitle) {
// Since this field expects raw html, safely escape the user data prior
$field->setRightTitle(Convert::raw2xml($right));
}
// if this field is required add some
if($editableField->Required) {
$field->addExtraClass('requiredField');
if($identifier = UserDefinedForm::config()->required_identifier) {
$title = $field->Title() ." <span class='required-identifier'>". $identifier . "</span>";
$field->setTitle($title);
}
}
// if this field has an extra class
if($extraClass = $editableField->ExtraClass) {
$field->addExtraClass(Convert::raw2att($extraClass));
}
// set the values passed by the url to the field
$request = $this->controller->getRequest();
if($value = $request->getVar($field->getName())) {
$field->setValue($value);
}
$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 FieldList
*/
public function getFormActions() {
$submitText = ($this->controller->SubmitButtonText) ? $this->controller->SubmitButtonText : _t('UserDefinedForm.SUBMITBUTTON', 'Submit');
$clearText = ($this->controller->ClearButtonText) ? $this->controller->ClearButtonText : _t('UserDefinedForm.CLEARBUTTON', 'Clear');
$actions = new FieldList(
new FormAction("process", $submitText)
);
if($this->controller->ShowClearButton) {
$actions->push(new ResetFormAction("clearForm", $clearText));
}
$this->extend('updateFormActions', $actions);
return $actions;
}
/**
* Get the required form fields for this form.
*
* @return RequiredFields
*/
public function getRequiredFields() {
// Generate required field validator
$requiredNames = $this
->controller
->Fields()
->filter('Required', true)
->column('Name');
$required = new RequiredFields($requiredNames);
$this->extend('updateRequiredFields', $required);
return $required;
}
/**
* Override validation so conditional fields can be validated correctly.
*
* @return boolean
*/
public function validate() {
$data = $this->getData();
Session::set("FormInfo.{$this->FormName()}.data", $data);
Session::clear("FormInfo.{$this->FormName()}.errors");
foreach ($this->controller->Fields() as $key => $field) {
$field->validateField($data, $this);
}
if(Session::get("FormInfo.{$this->FormName()}.errors")) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,60 @@
<?php
/**
* A custom rule for showing / hiding an EditableFormField
* based the value of another EditableFormField.
*
* @package userforms
*/
class EditableCustomRule extends DataObject {
private static $condition_options = array(
"IsBlank" => "Is blank",
"IsNotBlank" => "Is not blank",
"HasValue" => "Equals",
"ValueNot" => "Doesn't equal",
"ValueLessThan" => "Less than",
"ValueLessThanEqual" => "Less than or equal",
"ValueGreaterThan" => "Greater than",
"ValueGreaterThanEqual" => "Greater than or equal"
);
private static $db = array(
'Display' => 'Enum("Show,Hide")',
'ConditionOption' => 'Enum("IsBlank,IsNotBlank,HasValue,ValueNot,ValueLessThan,ValueLessThanEqual,ValueGreaterThan,ValueGreaterThanEqual")',
'FieldValue' => 'Varchar'
);
private static $has_one = array(
'Parent' => 'EditableFormField',
'ConditionField' => 'EditableFormField'
);
/**
* Built in extensions required
*
* @config
* @var array
*/
private static $extensions = array(
"Versioned('Stage', 'Live')"
);
/**
* Publish this custom rule to the live site
*
* Wrapper for the {@link Versioned} publish function
*/
public function doPublish($fromStage, $toStage, $createNewVersion = false) {
$this->publish($fromStage, $toStage, $createNewVersion);
}
/**
* Delete this custom rule from a given stage
*
* Wrapper for the {@link Versioned} deleteFromStage function
*/
public function doDeleteFromStage($stage) {
$this->deleteFromStage($stage);
}
}

View File

@ -16,20 +16,27 @@ class UserDefinedForm extends Page {
*/
private static $required_identifier = null;
/**
* Prevent translatable module from attepmting to translate FieldEditor
*
* @var array
* @config
*/
private static $translate_excluded_fields = array(
'Fields'
);
/**
* @var string
*/
private static $email_template_directory = 'userforms/templates/email/';
/**
* Should this module automatically upgrade on dev/build?
*
* @config
* @var bool
*/
private static $upgrade_on_build = true;
/**
* Built in extensions required by this page
* @config
* @var array
*/
private static $extensions = array(
'UserFormFieldEditorExtension'
);
/**
* @var array Fields on the user defined form page.
@ -60,7 +67,6 @@ class UserDefinedForm extends Page {
* @var array
*/
private static $has_many = array(
"Fields" => "EditableFormField",
"Submissions" => "SubmittedForm",
"EmailRecipients" => "UserDefinedForm_EmailRecipient"
);
@ -98,14 +104,10 @@ class UserDefinedForm extends Page {
$this->beforeUpdateCMSFields(function($fields) use ($self) {
// define tabs
$fields->findOrMakeTab('Root.FormContent', _t('UserDefinedForm.FORM', 'Form'));
$fields->findOrMakeTab('Root.FormOptions', _t('UserDefinedForm.CONFIGURATION', 'Configuration'));
$fields->findOrMakeTab('Root.Recipients', _t('UserDefinedForm.RECIPIENTS', 'Recipients'));
$fields->findOrMakeTab('Root.Submissions', _t('UserDefinedForm.SUBMISSIONS', 'Submissions'));
// field editor
$fields->addFieldToTab('Root.FormContent', new FieldEditor('Fields', 'Fields', '', $self ));
// text to show on complete
$onCompleteFieldSet = new CompositeField(
$label = new LabelField('OnCompleteMessageLabel',_t('UserDefinedForm.ONCOMPLETELABEL', 'Show on completion')),
@ -134,7 +136,7 @@ class UserDefinedForm extends Page {
$emailRecipients
->getConfig()
->getComponentByType('GridFieldDetailForm')
->setItemRequestClass('UserDefinedForm_EmailRecipient_ItemRequest');
->setItemRequestClass('UserFormRecipientItemRequest');
$fields->addFieldsToTab('Root.FormOptions', $onCompleteFieldSet);
$fields->addFieldToTab('Root.Recipients', $emailRecipients);
@ -209,125 +211,6 @@ SQL;
return $fields;
}
/**
* When publishing copy the editable form fields to the live database
* Not going to version emails and submissions as they are likely to
* persist over multiple versions.
*
* @return void
*/
public function doPublish() {
$parentID = (!empty($this->ID)) ? $this->ID : 0;
// remove fields on the live table which could have been orphaned.
$live = Versioned::get_by_stage("EditableFormField", "Live", "\"EditableFormField\".\"ParentID\" = $parentID");
if($live) {
foreach($live as $field) {
$field->doDeleteFromStage('Live');
}
}
// publish the draft pages
if($this->Fields()) {
foreach($this->Fields() as $field) {
$field->doPublish('Stage', 'Live');
}
}
parent::doPublish();
}
/**
* When un-publishing the page it has to remove all the fields from the
* live database table.
*
* @return void
*/
public function doUnpublish() {
if($this->Fields()) {
foreach($this->Fields() as $field) {
$field->doDeleteFromStage('Live');
}
}
parent::doUnpublish();
}
/**
* Roll back a form to a previous version.
*
* @param string|int Version to roll back to
*/
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()) {
foreach($this->Fields() as $field) {
// 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();
}
else {
Debug::show('deleting field'. $field->Name);
$this->Fields()->remove($field);
$field->delete();
$field->destroy();
}
}
}
// @todo Emails
}
*/
}
/**
* Revert the draft site to the current live site
*
* @return void
*/
public function doRevertToLive() {
if($this->Fields()) {
foreach($this->Fields() as $field) {
$field->publish("Live", "Stage", false);
$field->writeWithoutVersion();
}
}
parent::doRevertToLive();
}
/**
* Allow overriding the EmailRecipients on a {@link DataExtension}
@ -350,67 +233,6 @@ SQL;
return $recipients;
}
/**
* Store new and old ids of duplicated fields.
* This method also serves as a hook for descendant classes.
*/
protected function afterDuplicateField($page, $fromField, $toField) {
$this->fieldsFromTo[$fromField->ClassName . $fromField->ID] = $toField->ClassName . $toField->ID;
}
/**
* Duplicate this UserDefinedForm page, and its form fields.
* Submissions, on the other hand, won't be duplicated.
*
* @return Page
*/
public function duplicate($doWrite = true) {
$page = parent::duplicate($doWrite);
// the form fields
if($this->Fields()) {
foreach($this->Fields() as $field) {
$newField = $field->duplicate();
$newField->ParentID = $page->ID;
$newField->write();
$this->afterDuplicateField($page, $field, $newField);
}
}
// the emails
if($this->EmailRecipients()) {
foreach($this->EmailRecipients() as $email) {
$newEmail = $email->duplicate();
$newEmail->FormID = $page->ID;
$newEmail->write();
}
}
// Rewrite CustomRules
if($page->Fields()) {
foreach($page->Fields() as $field) {
// Rewrite name to make the CustomRules-rewrite below work.
$field->Name = $field->ClassName . $field->ID;
$rules = unserialize($field->CustomRules);
if (count($rules) && isset($rules[0]['ConditionField'])) {
$from = $rules[0]['ConditionField'];
if (array_key_exists($from, $this->fieldsFromTo)) {
$rules[0]['ConditionField'] = $this->fieldsFromTo[$from];
$field->CustomRules = serialize($rules);
}
}
$field->Write();
}
}
return $page;
}
/**
* Custom options for the form. You can extend the built in options by
* using {@link updateFormOptions()}
@ -436,36 +258,6 @@ SQL;
return $options;
}
/**
* Return if this form has been modified on the stage site and not published.
* this is used on the workflow module and for a couple highlighting things
*
* @return boolean
*/
public function getIsModifiedOnStage() {
// new unsaved pages could be never be published
if($this->isNew()) {
return false;
}
$stageVersion = Versioned::get_versionnumber_by_stage('UserDefinedForm', 'Stage', $this->ID);
$liveVersion = Versioned::get_versionnumber_by_stage('UserDefinedForm', 'Live', $this->ID);
$isModified = ($stageVersion && $stageVersion != $liveVersion);
if(!$isModified) {
if($this->Fields()) {
foreach($this->Fields() as $field) {
if($field->getIsModifiedOnStage()) {
$isModified = true;
break;
}
}
}
}
return $isModified;
}
/**
* Get the HTML id of the error container displayed above the form.
@ -475,6 +267,22 @@ SQL;
public function getErrorContainerID() {
return $this->config()->error_container_id;
}
public function requireDefaultRecords() {
parent::requireDefaultRecords();
if(!$this->config()->upgrade_on_build) {
return;
}
// Perform migrations
Injector::inst()
->create('UserFormsUpgradeService')
->setQuiet(true)
->run();
DB::alteration_message('Migrated userforms', 'changed');
}
}
/**
@ -552,141 +360,23 @@ class UserDefinedForm_Controller extends Page_Controller {
* Get the form for the page. Form can be modified by calling {@link updateForm()}
* on a UserDefinedForm extension.
*
* @return Form|false
* @return Forms
*/
public function Form() {
$fields = $this->getFormFields();
if(!$fields || !$fields->exists()) return false;
$actions = $this->getFormActions();
// get the required fields including the validation
$required = $this->getRequiredFields();
$form = UserForm::create($this);
// generate the conditional logic
$this->generateConditionalJavascript();
$form = new Form($this, "Form", $fields, $actions, $required);
$form->setRedirectToFormOnValidationError(true);
$data = Session::get("FormInfo.{$form->FormName()}.data");
if(is_array($data)) $form->loadDataFrom($data);
$this->extend('updateForm', $form);
if($this->DisableCsrfSecurityToken) {
$form->disableSecurityToken();
}
$this->generateValidationJavascript($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 FieldList
*/
public function getFormFields() {
$fields = new FieldList();
$editableFields = $this->Fields();
if($editableFields) foreach($editableFields as $editableField) {
// get the raw form field from the editable version
$field = $editableField->getFormField();
if(!$field) break;
// set the error / formatting messages
$field->setCustomValidationMessage($editableField->getErrorMessage());
// set the right title on this field
if($right = $editableField->getSetting('RightTitle')) {
// Since this field expects raw html, safely escape the user data prior
$field->setRightTitle(Convert::raw2xml($right));
}
// if this field is required add some
if($editableField->Required) {
$field->addExtraClass('requiredField');
if($identifier = UserDefinedForm::config()->required_identifier) {
$title = $field->Title() ." <span class='required-identifier'>". $identifier . "</span>";
$field->setTitle($title);
}
}
// if this field has an extra class
if($extraClass = $editableField->getSetting('ExtraClass')) {
$field->addExtraClass(Convert::raw2att($extraClass));
}
// set the values passed by the url to the field
$request = $this->getRequest();
if($value = $request->getVar($field->getName())) {
$field->setValue($value);
}
$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 FieldList
*/
public function getFormActions() {
$submitText = ($this->SubmitButtonText) ? $this->SubmitButtonText : _t('UserDefinedForm.SUBMITBUTTON', 'Submit');
$clearText = ($this->ClearButtonText) ? $this->ClearButtonText : _t('UserDefinedForm.CLEARBUTTON', 'Clear');
$actions = new FieldList(
new FormAction("process", $submitText)
);
if($this->ShowClearButton) {
$actions->push(new ResetFormAction("clearForm", $clearText));
}
$this->extend('updateFormActions', $actions);
return $actions;
}
/**
* Get the required form fields for this form.
*
* @return RequiredFields
*/
public function getRequiredFields() {
// Generate required field validator
$requiredNames = $this
->Fields()
->filter('Required', true)
->column('Name');
$required = new RequiredFields($requiredNames);
$this->extend('updateRequiredFields', $required);
return $required;
}
/**
* Build jQuery validation script and require as a custom script
*
* @param Form $form
* @param UserForm $form
*/
public function generateValidationJavascript($form) {
public function generateValidationJavascript(UserForm $form) {
// set the custom script for this form
Requirements::customScript(
$this
@ -711,139 +401,125 @@ class UserDefinedForm_Controller extends Page_Controller {
if($this->Fields()) {
foreach($this->Fields() as $field) {
$fieldId = $field->Name;
if($field->ClassName == 'EditableFormHeading') {
$fieldId = 'Form_Form_'.$field->Name;
if($field instanceof EditableFormHeading) {
$fieldId = 'UserForm_Form_' . $field->Name;
}
// Is this Field Show by Default
if(!$field->getShowOnLoad()) {
if(!$field->ShowOnLoad) {
$default .= "$(\"#" . $fieldId . "\").hide();\n";
}
// Check for field dependencies / default
if($field->Dependencies()) {
foreach($field->Dependencies() as $dependency) {
if(is_array($dependency) && isset($dependency['ConditionField']) && $dependency['ConditionField'] != "") {
// get the field which is effected
$formName = Convert::raw2sql($dependency['ConditionField']);
$formFieldWatch = DataObject::get_one("EditableFormField", "\"Name\" = '$formName'");
if(!$formFieldWatch) break;
// watch out for multiselect options - radios and check boxes
if(is_a($formFieldWatch, 'EditableDropdown')) {
$fieldToWatch = "$(\"select[name='".$dependency['ConditionField']."']\")";
$fieldToWatchOnLoad = $fieldToWatch;
}
// watch out for checkboxs as the inputs don't have values but are 'checked
else if(is_a($formFieldWatch, 'EditableCheckboxGroupField')) {
$fieldToWatch = "$(\"input[name='".$dependency['ConditionField']."[".$dependency['Value']."]']\")";
$fieldToWatchOnLoad = $fieldToWatch;
}
else if(is_a($formFieldWatch, 'EditableRadioField')) {
$fieldToWatch = "$(\"input[name='".$dependency['ConditionField']."']\")";
// We only want to trigger on load once for the radio group - hence we focus on the first option only.
$fieldToWatchOnLoad = "$(\"input[name='".$dependency['ConditionField']."']:first\")";
}
else {
$fieldToWatch = "$(\"input[name='".$dependency['ConditionField']."']\")";
$fieldToWatchOnLoad = $fieldToWatch;
}
// show or hide?
$view = (isset($dependency['Display']) && $dependency['Display'] == "Hide") ? "hide" : "show";
$opposite = ($view == "show") ? "hide" : "show";
// what action do we need to keep track of. Something nicer here maybe?
// @todo encapulsation
$action = "change";
if($formFieldWatch->ClassName == "EditableTextField") {
$action = "keyup";
}
// is this field a special option field
$checkboxField = false;
$radioField = false;
foreach($field->DisplayRules() as $rule) {
if(in_array($formFieldWatch->ClassName, array('EditableCheckboxGroupField', 'EditableCheckbox'))) {
$action = "click";
$checkboxField = true;
}
else if ($formFieldWatch->ClassName == "EditableRadioField") {
$radioField = true;
}
// Get the field which is effected
$formFieldWatch = EditableFormField::get()->byId($rule->ConditionFieldID);
// Escape the values.
$dependency['Value'] = str_replace('"', '\"', $dependency['Value']);
// and what should we evaluate
switch($dependency['ConditionOption']) {
case 'IsNotBlank':
$expression = ($checkboxField || $radioField) ? '$(this).prop("checked")' :'$(this).val() != ""';
break;
case 'IsBlank':
$expression = ($checkboxField || $radioField) ? '!($(this).prop("checked"))' : '$(this).val() == ""';
break;
case 'HasValue':
if ($checkboxField) {
$expression = '$(this).prop("checked")';
} else if ($radioField) {
// We cannot simply get the value of the radio group, we need to find the checked option first.
$expression = '$(this).parents(".field, .control-group").find("input:checked").val()=="'. $dependency['Value'] .'"';
} else {
$expression = '$(this).val() == "'. $dependency['Value'] .'"';
}
break;
case 'ValueLessThan':
$expression = '$(this).val() < parseFloat("'. $dependency['Value'] .'")';
break;
case 'ValueLessThanEqual':
$expression = '$(this).val() <= parseFloat("'. $dependency['Value'] .'")';
break;
case 'ValueGreaterThan':
$expression = '$(this).val() > parseFloat("'. $dependency['Value'] .'")';
break;
case 'ValueGreaterThanEqual':
$expression = '$(this).val() >= parseFloat("'. $dependency['Value'] .'")';
break;
default: // ==HasNotValue
if ($checkboxField) {
$expression = '!$(this).prop("checked")';
} else if ($radioField) {
// We cannot simply get the value of the radio group, we need to find the checked option first.
$expression = '$(this).parents(".field, .control-group").find("input:checked").val()!="'. $dependency['Value'] .'"';
} else {
$expression = '$(this).val() != "'. $dependency['Value'] .'"';
}
break;
}
if(!isset($watch[$fieldToWatch])) {
$watch[$fieldToWatch] = array();
}
$watch[$fieldToWatch][] = array(
'expression' => $expression,
'field_id' => $fieldId,
'view' => $view,
'opposite' => $opposite,
'action' => $action
);
$watchLoad[$fieldToWatchOnLoad] = true;
}
if($formFieldWatch->RecordClassName == 'EditableDropdown') {
// watch out for multiselect options - radios and check boxes
$fieldToWatch = "$(\"select[name='" . $formFieldWatch->Name . "']\")";
$fieldToWatchOnLoad = $fieldToWatch;
} else if($formFieldWatch->RecordClassName == 'EditableCheckboxGroupField') {
// watch out for checkboxs as the inputs don't have values but are 'checked
$fieldToWatch = "$(\"input[name='" . $formFieldWatch->Name . "[" . $rule->FieldValue . "]']\")";
$fieldToWatchOnLoad = $fieldToWatch;
} else if($formFieldWatch->RecordClassName == 'EditableRadioField') {
$fieldToWatch = "$(\"input[name='" . $formFieldWatch->Name . "']\")";
// We only want to trigger on load once for the radio group - hence we focus on the first option only.
$fieldToWatchOnLoad = "$(\"input[name='" . $formFieldWatch->Name . "']:first\")";
} else {
$fieldToWatch = "$(\"input[name='" . $formFieldWatch->Name . "']\")";
$fieldToWatchOnLoad = $fieldToWatch;
}
// show or hide?
$view = ($rule->Display == 'Hide') ? 'hide' : 'show';
$opposite = ($view == "show") ? "hide" : "show";
// what action do we need to keep track of. Something nicer here maybe?
// @todo encapulsation
$action = "change";
if($formFieldWatch->ClassName == "EditableTextField") {
$action = "keyup";
}
// is this field a special option field
$checkboxField = false;
$radioField = false;
if(in_array($formFieldWatch->ClassName, array('EditableCheckboxGroupField', 'EditableCheckbox'))) {
$action = "click";
$checkboxField = true;
} else if ($formFieldWatch->ClassName == "EditableRadioField") {
$radioField = true;
}
// and what should we evaluate
switch($rule->ConditionOption) {
case 'IsNotBlank':
$expression = ($checkboxField || $radioField) ? '$(this).prop("checked")' :'$(this).val() != ""';
break;
case 'IsBlank':
$expression = ($checkboxField || $radioField) ? '!($(this).prop("checked"))' : '$(this).val() == ""';
break;
case 'HasValue':
if ($checkboxField) {
$expression = '$(this).prop("checked")';
} else if ($radioField) {
// We cannot simply get the value of the radio group, we need to find the checked option first.
$expression = '$(this).parents(".field, .control-group").find("input:checked").val()=="'. $rule->FieldValue .'"';
} else {
$expression = '$(this).val() == "'. $rule->FieldValue .'"';
}
break;
case 'ValueLessThan':
$expression = '$(this).val() < parseFloat("'. $rule->FieldValue .'")';
break;
case 'ValueLessThanEqual':
$expression = '$(this).val() <= parseFloat("'. $rule->FieldValue .'")';
break;
case 'ValueGreaterThan':
$expression = '$(this).val() > parseFloat("'. $rule->FieldValue .'")';
break;
case 'ValueGreaterThanEqual':
$expression = '$(this).val() >= parseFloat("'. $rule->FieldValue .'")';
break;
default: // ==HasNotValue
if ($checkboxField) {
$expression = '!$(this).prop("checked")';
} else if ($radioField) {
// We cannot simply get the value of the radio group, we need to find the checked option first.
$expression = '$(this).parents(".field, .control-group").find("input:checked").val()!="'. $rule->FieldValue .'"';
} else {
$expression = '$(this).val() != "'. $rule->FieldValue .'"';
}
break;
}
if(!isset($watch[$fieldToWatch])) {
$watch[$fieldToWatch] = array();
}
$watch[$fieldToWatch][] = array(
'expression' => $expression,
'field_id' => $fieldId,
'view' => $view,
'opposite' => $opposite,
'action' => $action
);
$watchLoad[$fieldToWatchOnLoad] = true;
}
}
}
@ -902,27 +578,7 @@ JS
, 'UserFormsConditional');
}
}
/**
* Convert a PHP array to a JSON string. We cannot use {@link Convert::array2json}
* as it escapes our values with "" which appears to break the validate plugin
*
* @param Array array to convert
* @return JSON
*/
public function array2json($array) {
foreach($array as $key => $value) {
if(is_array( $value )) {
$result[] = "$key:" . $this->array2json($value);
} else {
$value = ( is_bool($value) || is_numeric($value) ) ? $value : "\"$value\"";
$result[] = "$key:$value";
}
}
return (isset($result)) ? "{\n".implode( ', ', $result ) ."\n}\n": '{}';
}
/**
* Process the form that is submitted through the site
*
@ -934,12 +590,12 @@ JS
public function process($data, $form) {
Session::set("FormInfo.{$form->FormName()}.data",$data);
Session::clear("FormInfo.{$form->FormName()}.errors");
foreach($this->Fields() as $field) {
$messages[$field->Name] = $field->getErrorMessage()->HTML();
$formField = $field->getFormField();
if($field->Required && $field->CustomRules()->Count() == 0) {
if($field->Required && $field->DisplayRules()->Count() == 0) {
if(isset($data[$field->Name])) {
$formField->setValue($data[$field->Name]);
}
@ -953,13 +609,13 @@ JS
}
}
}
if(Session::get("FormInfo.{$form->FormName()}.errors")){
Controller::curr()->redirectBack();
return;
}
$submittedForm = Object::create('SubmittedForm');
$submittedForm->SubmittedByID = ($id = Member::currentUserID()) ? $id : 0;
$submittedForm->ParentID = $this->ID;
@ -968,22 +624,22 @@ JS
if(!$this->DisableSaveSubmissions) {
$submittedForm->write();
}
$values = array();
$attachments = array();
$submittedFields = new ArrayList();
foreach($this->Fields() as $field) {
if(!$field->showInReports()) {
continue;
}
$submittedField = $field->getSubmittedFormField();
$submittedField->ParentID = $submittedForm->ID;
$submittedField->Name = $field->Name;
$submittedField->Title = $field->getField('Title');
// save the value from the data
if($field->hasMethod('getValueFromData')) {
$submittedField->Value = $field->getValueFromData($data);
@ -1021,26 +677,26 @@ JS
}
}
}
$submittedField->extend('onPopulationFromField', $field);
if(!$this->DisableSaveSubmissions) {
$submittedField->write();
}
$submittedFields->push($submittedField);
}
$emailData = array(
"Sender" => Member::currentUser(),
"Fields" => $submittedFields
);
$this->extend('updateEmailData', $emailData, $attachments);
// email users on submit.
if($recipients = $this->FilteredEmailRecipients($data, $form)) {
$email = new UserDefinedForm_SubmittedFormEmail($submittedFields);
$email = new UserFormRecipientEmail($submittedFields);
$mergeFields = $this->getMergeFieldsMap($emailData['Fields']);
if($attachments) {
@ -1117,7 +773,7 @@ JS
}
}
}
$submittedForm->extend('updateAfterProcess');
Session::clear("FormInfo.{$form->FormName()}.errors");
@ -1125,7 +781,6 @@ JS
$referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
// set a session variable from the security ID to stop people accessing
// the finished method directly.
if(!$this->DisableAuthenicatedFinishAction) {
@ -1142,7 +797,7 @@ JS
}
}
}
if(!$this->DisableSaveSubmissions) {
Session::set('userformssubmission'. $this->ID, $submittedForm->ID);
}
@ -1210,499 +865,3 @@ JS
));
}
}
/**
* A Form can have multiply members / emails to email the submission
* to and custom subjects
*
* @package userforms
*/
class UserDefinedForm_EmailRecipient extends DataObject {
private static $db = array(
'EmailAddress' => 'Varchar(200)',
'EmailSubject' => 'Varchar(200)',
'EmailFrom' => 'Varchar(200)',
'EmailReplyTo' => 'Varchar(200)',
'EmailBody' => 'Text',
'EmailBodyHtml' => 'HTMLText',
'EmailTemplate' => 'Varchar',
'SendPlain' => 'Boolean',
'HideFormData' => 'Boolean',
'CustomRulesCondition' => 'Enum("And,Or")'
);
private static $has_one = array(
'Form' => 'UserDefinedForm',
'SendEmailFromField' => 'EditableFormField',
'SendEmailToField' => 'EditableFormField',
'SendEmailSubjectField' => 'EditableFormField'
);
private static $has_many = array(
'CustomRules' => 'UserDefinedForm_EmailRecipientCondition'
);
private static $summary_fields = array(
'EmailAddress',
'EmailSubject',
'EmailFrom'
);
public function summaryFields() {
$fields = parent::summaryFields();
if(isset($fields['EmailAddress'])) {
$fields['EmailAddress'] = _t('UserDefinedForm.EMAILADDRESS', 'Email');
}
if(isset($fields['EmailSubject'])) {
$fields['EmailSubject'] = _t('UserDefinedForm.EMAILSUBJECT', 'Subject');
}
if(isset($fields['EmailFrom'])) {
$fields['EmailFrom'] = _t('UserDefinedForm.EMAILFROM', 'From');
}
return $fields;
}
/**
* Get instance of UserDefinedForm when editing in getCMSFields
*
* @return UserDefinedFrom
*/
protected function getFormParent() {
$formID = $this->FormID
? $this->FormID
: Session::get('CMSMain.currentPage');
return UserDefinedForm::get()->byID($formID);
}
public function getTitle() {
if($this->EmailAddress) {
return $this->EmailAddress;
}
if($this->EmailSubject) {
return $this->EmailSubject;
}
return parent::getTitle();
}
/**
* Generate a gridfield config for editing filter rules
*
* @return GridFieldConfig
*/
protected function getRulesConfig() {
$formFields = $this->getFormParent()->Fields();
$config = GridFieldConfig::create()
->addComponents(
new GridFieldButtonRow('before'),
new GridFieldToolbarHeader(),
new GridFieldAddNewInlineButton(),
new GridState_Component(),
new GridFieldDeleteAction(),
$columns = new GridFieldEditableColumns()
);
$columns->setDisplayFields(array(
'ConditionFieldID' => function($record, $column, $grid) use ($formFields) {
return DropdownField::create($column, false, $formFields->map('ID', 'Title'));
},
'ConditionOption' => function($record, $column, $grid) {
$options = UserDefinedForm_EmailRecipientCondition::config()->condition_options;
return DropdownField::create($column, false, $options);
},
'ConditionValue' => function($record, $column, $grid) {
return TextField::create($column);
}
));
return $config;
}
/**
* @return FieldList
*/
public function getCMSFields() {
// Determine optional field values
$form = $this->getFormParent();
// predefined choices are also candidates
$multiOptionFields = EditableMultipleOptionField::get()->filter('ParentID', $form->ID);
// if they have email fields then we could send from it
$validEmailFromFields = EditableEmailField::get()->filter('ParentID', $form->ID);
// For the subject, only one-line entry boxes make sense
$validSubjectFields = EditableTextField::get()
->filter('ParentID', $form->ID)
->filterByCallback(function($item, $list) {
return (int)$item->getSetting('Rows') === 1;
});
$validSubjectFields->merge($multiOptionFields);
// To address can only be email fields or multi option fields
$validEmailToFields = new ArrayList($validEmailFromFields->toArray());
$validEmailToFields->merge($multiOptionFields);
// Build fieldlist
$fields = FieldList::create(Tabset::create('Root')->addExtraClass('EmailRecipientForm'));
// Configuration fields
$fields->addFieldsToTab('Root.EmailDetails', array(
// Subject
FieldGroup::create(
TextField::create('EmailSubject', _t('UserDefinedForm.TYPESUBJECT', 'Type subject'))
->setAttribute('style', 'min-width: 400px;'),
DropdownField::create(
'SendEmailSubjectFieldID',
_t('UserDefinedForm.SELECTAFIELDTOSETSUBJECT', '.. or select a field to use as the subject'),
$validSubjectFields->map('ID', 'Title')
)->setEmptyString('')
)
->setTitle(_t('UserDefinedForm.EMAILSUBJECT', 'Email subject')),
// To
FieldGroup::create(
TextField::create('EmailAddress', _t('UserDefinedForm.TYPETO', 'Type to address'))
->setAttribute('style', 'min-width: 400px;'),
DropdownField::create(
'SendEmailToFieldID',
_t('UserDefinedForm.ORSELECTAFIELDTOUSEASTO', '.. or select a field to use as the to address'),
$validEmailToFields->map('ID', 'Title')
)->setEmptyString(' ')
)
->setTitle(_t('UserDefinedForm.SENDEMAILTO','Send email to'))
->setDescription(_t(
'UserDefinedForm.SENDEMAILTO_DESCRIPTION',
'You may enter multiple email addresses as a comma separated list.'
)),
// From
TextField::create('EmailFrom', _t('UserDefinedForm.FROMADDRESS','Send email from'))
->setDescription(_t(
'UserDefinedForm.EmailFromContent',
"The from address allows you to set who the email comes from. On most servers this ".
"will need to be set to an email address on the same domain name as your site. ".
"For example on yoursite.com the from address may need to be something@yoursite.com. ".
"You can however, set any email address you wish as the reply to address."
)),
// Reply-To
FieldGroup::create(
TextField::create('EmailReplyTo', _t('UserDefinedForm.TYPEREPLY', 'Type reply address'))
->setAttribute('style', 'min-width: 400px;'),
DropdownField::create(
'SendEmailFromFieldID',
_t('UserDefinedForm.ORSELECTAFIELDTOUSEASFROM', '.. or select a field to use as reply to address'),
$validEmailFromFields->map('ID', 'Title')
)->setEmptyString(' ')
)
->setTitle(_t('UserDefinedForm.REPLYADDRESS', 'Email for reply to'))
->setDescription(_t(
'UserDefinedForm.REPLYADDRESS_DESCRIPTION',
'The email address which the recipient is able to \'reply\' to.'
))
));
// Only show the preview link if the recipient has been saved.
if (!empty($this->EmailTemplate)) {
$preview = sprintf(
'<p><a href="%s" target="_blank" class="ss-ui-button">%s</a></p><em>%s</em>',
"admin/pages/edit/EditForm/field/EmailRecipients/item/{$this->ID}/preview",
_t('UserDefinedForm.PREVIEW_EMAIL', 'Preview email'),
_t('UserDefinedForm.PREVIEW_EMAIL_DESCRIPTION', 'Note: Unsaved changes will not appear in the preview.')
);
} else {
$preview = sprintf(
'<em>%s</em>',
_t(
'UserDefinedForm.PREVIEW_EMAIL_UNAVAILABLE',
'You can preview this email once you have saved the Recipient.'
)
);
}
// Email templates
$fields->addFieldsToTab('Root.EmailContent', array(
new CheckboxField('HideFormData', _t('UserDefinedForm.HIDEFORMDATA', 'Hide form data from email?')),
new CheckboxField('SendPlain', _t('UserDefinedForm.SENDPLAIN', 'Send email as plain text? (HTML will be stripped)')),
new DropdownField('EmailTemplate', _t('UserDefinedForm.EMAILTEMPLATE', 'Email template'), $this->getEmailTemplateDropdownValues()),
new HTMLEditorField('EmailBodyHtml', _t('UserDefinedForm.EMAILBODYHTML','Body')),
new TextareaField('EmailBody', _t('UserDefinedForm.EMAILBODY','Body')),
new LiteralField('EmailPreview', '<div id="EmailPreview">' . $preview . '</div>')
));
// Custom rules for sending this field
$grid = new GridField(
"CustomRules",
_t('EditableFormField.CUSTOMRULES', 'Custom Rules'),
$this->CustomRules(),
$this->getRulesConfig()
);
$grid->setDescription(_t(
'UserDefinedForm.RulesDescription',
'Emails will only be sent to the recipient if the custom rules are met. If no rules are defined, this receipient will receive notifications for every submission.'
));
$fields->addFieldsToTab('Root.CustomRules', array(
new DropdownField(
'CustomRulesCondition',
_t('UserDefinedForm.SENDIF', 'Send condition'),
array(
'Or' => 'Any conditions are true',
'And' => 'All conditions are true'
)
),
$grid
));
$this->extend('updateCMSFields', $fields);
return $fields;
}
/**
* @param Member
*
* @return boolean
*/
public function canCreate($member = null) {
return $this->Form()->canCreate();
}
/**
* @param Member
*
* @return boolean
*/
public function canView($member = null) {
return $this->Form()->canView();
}
/**
* @param Member
*
* @return boolean
*/
public function canEdit($member = null) {
return $this->Form()->canEdit();
}
/**
* @param Member
*
* @return boolean
*/
public function canDelete($member = null) {
return $this->Form()->canDelete();
}
/*
* Determine if this recipient may receive notifications for this submission
*
* @param array $data
* @param Form $form
* @return bool
*/
public function canSend($data, $form) {
// Skip if no rules configured
$customRules = $this->CustomRules();
if(!$customRules->count()) {
return true;
}
// Check all rules
$isAnd = $this->CustomRulesCondition === 'And';
foreach($customRules as $customRule) {
$matches = $customRule->matches($data, $form);
if($isAnd && !$matches) {
return false;
}
if(!$isAnd && $matches) {
return true;
}
}
// Once all rules are checked
return $isAnd;
}
/**
* Make sure the email template saved against the recipient exists on the file system.
*
* @param string
*
* @return boolean
*/
public function emailTemplateExists($template = '') {
$t = ($template ? $template : $this->EmailTemplate);
return in_array($t, $this->getEmailTemplateDropdownValues());
}
/**
* Get the email body for the current email format
*
* @return string
*/
public function getEmailBodyContent() {
return $this->SendPlain ? $this->EmailBody : $this->EmailBodyHtml;
}
/**
* Gets a list of email templates suitable for populating the email template dropdown.
*
* @return array
*/
public function getEmailTemplateDropdownValues() {
$templates = array();
$finder = new SS_FileFinder();
$finder->setOption('name_regex', '/^.*\.ss$/');
$found = $finder->find(BASE_PATH . '/' . UserDefinedForm::config()->email_template_directory);
foreach ($found as $key => $value) {
$template = pathinfo($value);
$templates[$template['filename']] = $template['filename'];
}
return $templates;
}
}
/**
* Controller that handles requests to EmailRecipient's
*
* @package userforms
*/
class UserDefinedForm_EmailRecipient_ItemRequest extends GridFieldDetailForm_ItemRequest {
private static $allowed_actions = array(
'edit',
'view',
'ItemEditForm',
'preview'
);
public function edit($request) {
Requirements::javascript(USERFORMS_DIR . '/javascript/Recipient.js');
return parent::edit($request);
}
/**
* Renders a preview of the recipient email.
*/
public function preview() {
return $this->customise(new ArrayData(array(
'Body' => $this->record->getEmailBodyContent(),
'HideFormData' => $this->record->HideFormData,
'Fields' => $this->getPreviewFieldData()
)))->renderWith($this->record->EmailTemplate);
}
/**
* Get some placeholder field values to display in the preview
* @return ArrayList
*/
private function getPreviewFieldData() {
$data = new ArrayList();
$fields = $this->record->Form()->Fields()->filter(array(
'ClassName:not' => 'EditableLiteralField',
'ClassName:not' => 'EditableFormHeading'
));
foreach ($fields as $field) {
$data->push(new ArrayData(array(
'Name' => $field->Name,
'Title' => $field->Title,
'Value' => '$' . $field->Name,
'FormattedValue' => '$' . $field->Name
)));
}
return $data;
}
}
/**
* Declares a condition that determines whether an email can be sent to a given recipient
*/
class UserDefinedForm_EmailRecipientCondition extends DataObject {
/**
* List of options
*
* @config
* @var array
*/
private static $condition_options = array(
"IsBlank" => "Is blank",
"IsNotBlank" => "Is not blank",
"Equals" => "Equals",
"NotEquals" => "Doesn't equal"
);
private static $db = array(
'ConditionOption' => 'Enum("IsBlank,IsNotBlank,Equals,NotEquals")',
'ConditionValue' => 'Varchar'
);
private static $has_one = array(
'Parent' => 'UserDefinedForm_EmailRecipient',
'ConditionField' => 'EditableFormField'
);
/**
* Determine if this rule matches the given condition
*
* @param array $data
* @param Form $form
* @return bool
*/
public function matches($data, $form) {
$fieldName = $this->ConditionField()->Name;
$fieldValue = isset($data[$fieldName]) ? $data[$fieldName] : null;
switch($this->ConditionOption) {
case 'IsBlank':
return empty($fieldValue);
case 'IsNotBlank':
return !empty($fieldValue);
default:
$matches = is_array($fieldValue)
? in_array($this->ConditionValue, $fieldValue)
: $this->ConditionValue === (string)$fieldValue;
return ($this->ConditionOption === 'Equals') === (bool)$matches;
}
}
}
/**
* Email that gets sent to the people listed in the Email Recipients when a
* submission is made.
*
* @package userforms
*/
class UserDefinedForm_SubmittedFormEmail extends Email {
protected $ss_template = "SubmittedFormEmail";
protected $data;
public function __construct($submittedFields = null) {
parent::__construct($submittedFields = null);
}
/**
* Set the "Reply-To" header with an email address rather than append as
* {@link Email::replyTo} does.
*
* @param string $email The email address to set the "Reply-To" header to
*/
public function setReplyTo($email) {
$this->customHeaders['Reply-To'] = $email;
}
}

View File

@ -12,17 +12,27 @@ class EditableCheckbox extends EditableFormField {
private static $singular_name = 'Checkbox Field';
private static $plural_name = 'Checkboxes';
public function getFieldConfiguration() {
$options = parent::getFieldConfiguration();
$options->push(new CheckboxField("Fields[$this->ID][CustomSettings][Default]", _t('EditableFormField.CHECKEDBYDEFAULT', 'Checked by Default?'), $this->getSetting('Default')));
return $options;
private static $db = array(
'CheckedDefault' => 'Boolean' // from CustomSettings
);
/**
* @return FieldList
*/
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->replaceField('Default', CheckboxField::create(
"CheckedDefault",
_t('EditableFormField.CHECKEDBYDEFAULT', 'Checked by Default?')
));
return $fields;
}
public function getFormField() {
$field = CheckboxField::create( $this->Name, $this->Title, $this->getSetting('Default'));
$field = CheckboxField::create($this->Name, $this->Title, $this->CheckedDefault);
if ($this->Required) {
// Required validation can conflict so add the Required validation messages
@ -40,4 +50,14 @@ class EditableCheckbox extends EditableFormField {
return ($value) ? _t('EditableFormField.YES', 'Yes') : _t('EditableFormField.NO', 'No');
}
}
public function migrateSettings($data) {
// Migrate 'Default' setting to 'CheckedDefault'
if(isset($data['Default'])) {
$this->CheckedDefault = (bool)$data['Default'];
unset($data['Default']);
}
parent::migrateSettings($data);
}
}

View File

@ -15,11 +15,17 @@ class EditableCheckboxGroupField extends EditableMultipleOptionField {
public function getFormField() {
$optionSet = $this->Options();
$options = array();
$optionMap = $optionSet->map('EscapedTitle', 'Title');
$optionMap = ($optionSet) ? $optionSet->map('EscapedTitle', 'Title') : array();
$field = new UserFormsCheckboxSetField($this->Name, $this->Title, $optionMap);
return new UserFormsCheckboxSetField($this->Name, $this->Title, $optionMap);
// Set the default checked items
$defaultCheckedItems = $optionSet->filter('Default', 1);
if ($defaultCheckedItems->count()) {
$field->setDefaultItems($defaultCheckedItems->map('EscapedTitle')->keys());
}
return $field;
}
public function getValueFromData($data) {

View File

@ -10,9 +10,20 @@ class EditableCountryDropdownField extends EditableFormField {
private static $singular_name = 'Country Dropdown';
private static $plural_name = 'Country Dropdowns';
/**
* @return FieldList
*/
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeByName('Default');
return $fields;
}
public function getFormField() {
return new CountryDropdownField($this->Name, $this->Title);
return CountryDropdownField::create($this->Name, $this->Title);
}
public function getValueFromData($data) {

View File

@ -12,24 +12,27 @@ class EditableDateField extends EditableFormField {
private static $singular_name = 'Date Field';
private static $plural_name = 'Date Fields';
public function getFieldConfiguration() {
$default = ($this->getSetting('DefaultToToday')) ? $this->getSetting('DefaultToToday') : false;
$label = _t('EditableFormField.DEFAULTTOTODAY', 'Default to Today?');
return new FieldList(
new CheckboxField($this->getSettingName("DefaultToToday"), $label, $default)
);
}
public function populateFromPostData($data) {
$fieldPrefix = 'Default-';
if(empty($data['Default']) && !empty($data[$fieldPrefix.'Year']) && !empty($data[$fieldPrefix.'Month']) && !empty($data[$fieldPrefix.'Day'])) {
$data['Default'] = $data['Year'] . '-' . $data['Month'] . '-' . $data['Day'];
}
parent::populateFromPostData($data);
private static $db = array(
'DefaultToToday' => 'Boolean' // From customsettings
);
/**
* @return FieldList
*/
public function getCMSFields() {
$this->beforeUpdateCMSFields(function(FieldList $fields) {
$fields->addFieldToTab(
'Root.Main',
CheckboxField::create(
'DefaultToToday',
_t('EditableFormField.DEFAULTTOTODAY', 'Default to Today?')
),
'RightTitle'
);
});
return parent::getCMSFields();
}
/**
@ -37,7 +40,9 @@ class EditableDateField extends EditableFormField {
*
*/
public function getFormField() {
$defaultValue = ($this->getSetting('DefaultToToday')) ? date('Y-m-d') : $this->Default;
$defaultValue = $this->DefaultToToday
? SS_Datetime::now()->Format('Y-m-d')
: $this->Default;
$field = EditableDateField_FormField::create( $this->Name, $this->Title, $defaultValue);
$field->setConfig('showcalendar', true);

View File

@ -12,12 +12,24 @@ class EditableDropdown extends EditableMultipleOptionField {
private static $singular_name = 'Dropdown Field';
private static $plural_name = 'Dropdowns';
/**
* @return FieldList
*/
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeByName('Default');
return $fields;
}
/**
* @return DropdownField
*/
public function getFormField() {
public function getFormField() {
$optionSet = $this->Options();
$defaultOptions = $optionSet->filter('Default', 1);
$options = array();
if($optionSet) {
@ -35,6 +47,10 @@ class EditableDropdown extends EditableMultipleOptionField {
$field->setAttribute('data-rule-required', 'true');
$field->setAttribute('data-msg-required', $errorMessage);
}
if($defaultOptions->count()) {
$field->setValue($defaultOptions->First()->EscapedTitle);
}
return $field;
}

View File

@ -28,6 +28,8 @@ class EditableEmailField extends EditableFormField {
$field->setAttribute('data-rule-required', 'true');
$field->setAttribute('data-msg-required', $errorMessage);
}
$field->setValue($this->Default);
return $field;
}

View File

@ -12,21 +12,26 @@ class EditableFileField extends EditableFormField {
private static $plural_names = 'File Fields';
public function getFieldConfiguration() {
$field = parent::getFieldConfiguration();
$folder = ($this->getSetting('Folder')) ? $this->getSetting('Folder') : null;
private static $has_one = array(
'Folder' => 'Folder' // From CustomFields
);
$tree = UserformsTreeDropdownField::create(
$this->getSettingName("Folder"),
_t('EditableUploadField.SELECTUPLOADFOLDER', 'Select upload folder'),
"Folder"
/**
* @return FieldList
*/
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab(
'Root.Main',
TreeDropdownField::create(
'FolderID',
_t('EditableUploadField.SELECTUPLOADFOLDER', 'Select upload folder'),
'Folder'
)
);
$tree->setValue($folder);
$field->push($tree);
return $field;
return $fields;
}
public function getFormField() {
@ -37,14 +42,11 @@ class EditableFileField extends EditableFormField {
array_filter(Config::inst()->get('File', 'allowed_extensions'))
);
if($this->getSetting('Folder')) {
$folder = Folder::get()->byId($this->getSetting('Folder'));
if($folder) {
$field->setFolderName(
preg_replace("/^assets\//","", $folder->Filename)
);
}
$folder = $this->Folder();
if($folder && $folder->exists()) {
$field->setFolderName(
preg_replace("/^assets\//","", $folder->Filename)
);
}
if ($this->Required) {
@ -72,4 +74,15 @@ class EditableFileField extends EditableFormField {
public function getSubmittedFormField() {
return new SubmittedFileField();
}
public function migrateSettings($data) {
// Migrate 'Folder' setting to 'FolderID'
if(isset($data['Folder'])) {
$this->FolderID = $data['Folder'];
unset($data['Folder']);
}
parent::migrateSettings($data);
}
}

View File

@ -4,11 +4,17 @@
* object like {@link EditableTextField}.
*
* @package userforms
* @method DataList DisplayRules() List of EditableCustomRule objects
*/
class EditableFormField extends DataObject {
private static $default_sort = "Sort";
/**
* Default sort order
*
* @config
* @var string
*/
private static $default_sort = '"Sort"';
/**
* A list of CSS classes that can be added
@ -16,32 +22,67 @@ class EditableFormField extends DataObject {
* @var array
*/
public static $allowed_css = array();
/**
* @config
* @var array
*/
private static $summary_fields = array(
'Title'
);
/**
* @config
* @var array
*/
private static $db = array(
"Name" => "Varchar",
"Title" => "Varchar(255)",
"Default" => "Varchar",
"Default" => "Varchar(255)",
"Sort" => "Int",
"Required" => "Boolean",
"CustomErrorMessage" => "Varchar(255)",
"CustomRules" => "Text",
"CustomSettings" => "Text",
"CustomParameter" => "Varchar(200)"
"CustomRules" => "Text", // @deprecated from 2.0
"CustomSettings" => "Text", // @deprecated from 2.0
"Migrated" => "Boolean", // set to true when migrated
"ExtraClass" => "Text", // from CustomSettings
"RightTitle" => "Varchar(255)", // from CustomSettings
"ShowOnLoad" => "Boolean(1)", // from CustomSettings
);
/**
* @config
* @var array
*/
private static $has_one = array(
"Parent" => "UserDefinedForm",
);
/**
* Built in extensions required
*
* @config
* @var array
*/
private static $extensions = array(
"Versioned('Stage', 'Live')"
);
/**
* @config
* @var array
*/
private static $has_many = array(
"DisplayRules" => "EditableCustomRule.Parent" // from CustomRules
);
/**
* @var bool
*/
protected $readonly;
/**
* Set the visibility of an individual form field
*
@ -59,14 +100,163 @@ class EditableFormField extends DataObject {
private function isReadonly() {
return $this->readonly;
}
/**
* Template to render the form field into
*
* @return String
* @return FieldList
*/
public function EditSegment() {
return $this->renderWith('EditableFormField');
public function getCMSFields() {
$fields = new FieldList(new TabSet('Root'));
// Main tab
$fields->addFieldsToTab(
'Root.Main',
array(
ReadonlyField::create(
'Type',
_t('EditableFormField.TYPE', 'Type'),
$this->i18n_singular_name()
),
LiteralField::create(
'MergeField',
_t(
'EditableFormField.MERGEFIELDNAME',
'<div class="field readonly">' .
'<label class="left">Merge field</label>' .
'<div class="middleColumn">' .
'<span class="readonly">$' . $this->Name . '</span>' .
'</div>' .
'</div>'
)
),
TextField::create('Title'),
TextField::create('Default', _t('EditableFormField.DEFAULT', 'Default value')),
TextField::create('RightTitle', _t('EditableFormField.RIGHTTITLE', 'Right title'))
)
);
// Custom settings
if (!empty(self::$allowed_css)) {
$cssList = array();
foreach(self::$allowed_css as $k => $v) {
if (!is_array($v)) {
$cssList[$k]=$v;
} elseif ($k === $this->ClassName) {
$cssList = array_merge($cssList, $v);
}
}
$fields->addFieldToTab('Root.Main',
DropdownField::create(
'ExtraClass',
_t('EditableFormField.EXTRACLASS_TITLE', 'Extra Styling/Layout'),
$cssList
)->setDescription(_t(
'EditableFormField.EXTRACLASS_SELECT',
'Select from the list of allowed styles'
))
);
} else {
$fields->addFieldToTab('Root.Main',
TextField::create(
'ExtraClass',
_t('EditableFormField.EXTRACLASS_Title', 'Extra CSS Classes')
)->setDescription(_t(
'EditableFormField.EXTRACLASS_MULTIPLE',
'Separate each CSS class with a single space'
))
);
}
// Validation
$fields->addFieldsToTab(
'Root.Validation',
$this->getFieldValidationOptions()
);
$editableColumns = new GridFieldEditableColumns();
$editableColumns->setDisplayFields(array(
'Display' => '',
'ConditionFieldID' => function($record, $column, $grid) {
return DropdownField::create(
$column,
'',
EditableFormField::get()
->filter(array(
'ParentID' => $this->ParentID
))
->exclude(array(
'ID' => $this->ID
))
->map('ID', 'Title')
);
},
'ConditionOption' => function($record, $column, $grid) {
$options = Config::inst()->get('EditableCustomRule', 'condition_options');
return DropdownField::create($column, '', $options);
},
'FieldValue' => function($record, $column, $grid) {
return TextField::create($column);
},
'ParentID' => function($record, $column, $grid) {
return HiddenField::create($column, '', $this->ID);
}
));
// Custom rules
$customRulesConfig = GridFieldConfig::create()
->addComponents(
$editableColumns,
new GridFieldButtonRow(),
new GridFieldToolbarHeader(),
new GridFieldAddNewInlineButton(),
new GridFieldDeleteAction(),
new GridState_Component()
);
$fields->addFieldsToTab('Root.DisplayRules', array(
CheckboxField::create('ShowOnLoad')
->setDescription(_t(
'EditableFormField.SHOWONLOAD',
'Initial visibility before processing these rules'
)),
GridField::create(
'DisplayRules',
_t('EditableFormField.CUSTOMRULES', 'Custom Rules'),
$this->DisplayRules(),
$customRulesConfig
)
));
$this->extend('updateCMSFields', $fields);
return $fields;
}
/**
* @return void
*/
public function onBeforeWrite() {
parent::onBeforeWrite();
if(!$this->Sort && $this->ParentID) {
$parentID = $this->ParentID;
$this->Sort = EditableFormField::get()
->filter('ParentID', $parentID)
->max('Sort') + 1;
}
}
/**
* @return void
*/
public function onAfterWrite() {
parent::onAfterWrite();
// Set a field name.
if(!$this->Name) {
$this->Name = $this->RecordClassName . $this->ID;
$this->write();
}
}
/**
@ -85,7 +275,11 @@ class EditableFormField extends DataObject {
* @return bool
*/
public function canDelete($member = null) {
return ($this->Parent()->canEdit($member = null) && !$this->isReadonly());
if($this->Parent()) {
return $this->Parent()->canEdit($member) && !$this->isReadonly();
}
return true;
}
/**
@ -95,7 +289,11 @@ class EditableFormField extends DataObject {
* @return bool
*/
public function canEdit($member = null) {
return ($this->Parent()->canEdit($member = null) && !$this->isReadonly());
if($this->Parent()) {
return $this->Parent()->canEdit($member) && !$this->isReadonly();
}
return true;
}
/**
@ -105,6 +303,11 @@ class EditableFormField extends DataObject {
*/
public function doPublish($fromStage, $toStage, $createNewVersion = false) {
$this->publish($fromStage, $toStage, $createNewVersion);
// Don't forget to publish the related custom rules...
foreach ($this->DisplayRules() as $rule) {
$rule->doPublish($fromStage, $toStage, $createNewVersion);
}
}
/**
@ -114,6 +317,11 @@ class EditableFormField extends DataObject {
*/
public function doDeleteFromStage($stage) {
$this->deleteFromStage($stage);
// Don't forget to delete the related custom rules...
foreach ($this->DisplayRules() as $rule) {
$rule->deleteFromStage($stage);
}
}
/**
@ -136,49 +344,32 @@ class EditableFormField extends DataObject {
if($this->isNew()) return false;
$stageVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Stage', $this->ID);
$liveVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Live', $this->ID);
$liveVersion = Versioned::get_versionnumber_by_stage('EditableFormField', 'Live', $this->ID);
return ($stageVersion && $stageVersion != $liveVersion);
}
/**
* Show this form on load or not
*
* @return bool
*/
public function getShowOnLoad() {
return ($this->getSetting('ShowOnLoad') == "Show" || $this->getSetting('ShowOnLoad') == '') ? true : false;
}
/**
* To prevent having tables for each fields minor settings we store it as
* a serialized array in the database.
*
* @return Array Return all the Settings
* @deprecated since version 4.0
*/
public function getSettings() {
Deprecation::notice('4.0', 'getSettings is deprecated');
return (!empty($this->CustomSettings)) ? unserialize($this->CustomSettings) : array();
}
/**
* Set the custom settings for this field as we store the minor details in
* a serialized array in the database
*
* @param Array the custom settings
* @deprecated since version 4.0
*/
public function setSettings($settings = array()) {
Deprecation::notice('4.0', 'setSettings is deprecated');
$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
* @deprecated since version 4.0
*/
public function setSetting($key, $value) {
Deprecation::notice('4.0', "setSetting({$key}) is deprecated");
$settings = $this->getSettings();
$settings[$key] = $value;
@ -199,13 +390,11 @@ class EditableFormField extends DataObject {
}
/**
* Return just one custom setting or empty string if it does
* not exist
*
* @param String Value to use as key
* @return String
* @deprecated since version 4.0
*/
public function getSetting($setting) {
Deprecation::notice("4.0", "getSetting({$setting}) is deprecated");
$settings = $this->getSettings();
if(isset($settings) && count($settings) > 0) {
if(isset($settings[$setting])) {
@ -243,51 +432,6 @@ class EditableFormField extends DataObject {
public function showExtraOptions() {
return true;
}
/**
* Return the custom validation fields for this field for the CMS
*
* @return array
*/
public function Dependencies() {
return ($this->CustomRules) ? unserialize($this->CustomRules) : array();
}
/**
* Return the custom validation fields for the field
*
* @return DataObjectSet
*/
public function CustomRules() {
$output = new ArrayList();
$fields = $this->Parent()->Fields();
// check for existing ones
if($rules = $this->Dependencies()) {
foreach($rules as $rule => $data) {
// recreate all the field object to prevent caching
$outputFields = new ArrayList();
foreach($fields as $field) {
$new = clone $field;
$new->isSelected = ($new->Name == $data['ConditionField']) ? true : false;
$outputFields->push($new);
}
$output->push(new ArrayData(array(
'FieldName' => $this->getFieldName(),
'Display' => $data['Display'],
'Fields' => $outputFields,
'ConditionField' => $data['ConditionField'],
'ConditionOption' => $data['ConditionOption'],
'Value' => $data['Value']
)));
}
}
return $output;
}
/**
* Title field of the field in the backend of the page
@ -307,164 +451,44 @@ class EditableFormField extends DataObject {
return $field;
}
/** Returns the Title for rendering in the front-end (with XML values escaped) */
/**
* Returns the Title for rendering in the front-end (with XML values escaped)
*
* @return string
*/
public function getTitle() {
return Convert::raw2att($this->getField('Title'));
}
/**
* Return the base name for this form field in the
* form builder. Optionally returns the name with the given field
*
* @param String Field Name
*
* @return String
* @deprecated since version 4.0
*/
public function getFieldName($field = false) {
Deprecation::notice('4.0', "getFieldName({$field}) is deprecated");
return ($field) ? "Fields[".$this->ID."][".$field."]" : "Fields[".$this->ID."]";
}
/**
* Generate a name for the Setting field
*
* @param String name of the setting
* @return String
* @deprecated since version 4.0
*/
public function getSettingName($field) {
Deprecation::notice('4.0', "getSettingName({$field}) is deprecated");
$name = $this->getFieldName('CustomSettings');
return $name . '[' . $field .']';
}
/**
* How to save the data submitted in this field into the database object
* which this field represents.
*
* Any class's which call this should also call
* {@link parent::populateFromPostData()} to ensure that this method is
* called
*
* @access public
*
* @param array $data
*/
public function populateFromPostData($data) {
$this->Title = (isset($data['Title'])) ? $data['Title']: "";
$this->Default = (isset($data['Default'])) ? $data['Default'] : "";
$this->Sort = (isset($data['Sort'])) ? $data['Sort'] : null;
$this->Required = !empty($data['Required']) ? 1 : 0;
$this->Name = $this->class.$this->ID;
$this->CustomRules = "";
$this->CustomErrorMessage = (isset($data['CustomErrorMessage'])) ? $data['CustomErrorMessage'] : "";
$this->CustomSettings = "";
// custom settings
if(isset($data['CustomSettings'])) {
$this->setSettings($data['CustomSettings']);
}
// custom validation
if(isset($data['CustomRules'])) {
$rules = array();
foreach($data['CustomRules'] as $key => $value) {
if(is_array($value)) {
$fieldValue = (isset($value['Value'])) ? $value['Value'] : '';
if(isset($value['ConditionOption']) && $value['ConditionOption'] == "Blank" || $value['ConditionOption'] == "NotBlank") {
$fieldValue = "";
}
$rules[] = array(
'Display' => (isset($value['Display'])) ? $value['Display'] : "",
'ConditionField' => (isset($value['ConditionField'])) ? $value['ConditionField'] : "",
'ConditionOption' => (isset($value['ConditionOption'])) ? $value['ConditionOption'] : "",
'Value' => $fieldValue
);
}
}
$this->CustomRules = serialize($rules);
}
$this->extend('onPopulateFromPostData', $data);
$this->write();
}
/**
* Implement custom field Configuration on this field. Includes such things as
* settings and options of a given editable form field
*
* @return FieldSet
*/
public function getFieldConfiguration() {
$extraClass = ($this->getSetting('ExtraClass')) ? $this->getSetting('ExtraClass') : '';
$mergeFieldName = new LiteralField('MergeFieldName', _t('EditableFormField.MERGEFIELDNAME',
'<div class="field">' .
'<label class="left" for="Fields-6-CustomSettings-RightTitle">Merge field</label>' .
'<div class="middleColumn">' .
'<p>$' . $this->Name . '</p>' .
'<em>Use this to display the field\'s value in email content.</em>' .
'</div>' .
'</div>'
));
if (is_array(self::$allowed_css) && !empty(self::$allowed_css)) {
foreach(self::$allowed_css as $k => $v) {
if (!is_array($v)) $cssList[$k]=$v;
elseif ($k == $this->ClassName()) $cssList = array_merge($cssList, $v);
}
$ec = new DropdownField(
$this->getSettingName('ExtraClass'),
_t('EditableFormField.EXTRACLASSA', 'Extra Styling/Layout'),
$cssList, $extraClass
);
}
else {
$ec = new TextField(
$this->getSettingName('ExtraClass'),
_t('EditableFormField.EXTRACLASSB', 'Extra css Class - separate multiples with a space'),
$extraClass
);
}
$right = new TextField(
$this->getSettingName('RightTitle'),
_t('EditableFormField.RIGHTTITLE', 'Right Title'),
$this->getSetting('RightTitle')
);
$fields = FieldList::create(
$mergeFieldName,
$ec,
$right
);
$this->extend('updateFieldConfiguration', $fields);
return $fields;
}
/**
* Append custom validation fields to the default 'Validation'
* section in the editable options view
*
* @return FieldSet
* @return FieldList
*/
public function getFieldValidationOptions() {
$fields = new FieldList(
new CheckboxField($this->getFieldName('Required'), _t('EditableFormField.REQUIRED', 'Is this field Required?'), $this->Required),
new TextField($this->getFieldName('CustomErrorMessage'), _t('EditableFormField.CUSTOMERROR','Custom Error Message'), $this->CustomErrorMessage)
CheckboxField::create('Required', _t('EditableFormField.REQUIRED', 'Is this field Required?')),
TextField::create('CustomErrorMessage', _t('EditableFormField.CUSTOMERROR','Custom Error Message'))
);
if(!$this->canEdit()) {
foreach($fields as $field) {
$field->performReadonlyTransformation();
}
}
$this->extend('updateFieldValidationOptions', $fields);
@ -533,4 +557,53 @@ class EditableFormField extends DataObject {
return DBField::create_field('Varchar', $errorMessage);
}
/**
* Validate the field taking into account its custom rules.
*
* @param Array $data
* @param UserForm $form
*
* @return boolean
*/
public function validateField($data, $form) {
if($this->Required && $this->DisplayRules()->Count() == 0) {
$formField = $this->getFormField();
if(isset($data[$this->Name])) {
$formField->setValue($data[$this->Name]);
}
if(
!isset($data[$this->Name]) ||
!$data[$this->Name] ||
!$formField->validate($form->getValidator())
) {
$form->addErrorMessage($this->Name, $this->getErrorMessage(), 'bad');
}
}
return true;
}
/**
* Invoked by UserFormUpgradeService to migrate settings specific to this field from CustomSettings
* to the field proper
*
* @param array $data Unserialised data
*/
public function migrateSettings($data) {
// Map 'Show' / 'Hide' to boolean
if(isset($data['ShowOnLoad'])) {
$this->ShowOnLoad = $data['ShowOnLoad'] === '' || ($data['ShowOnLoad'] && $data['ShowOnLoad'] !== 'Hide');
unset($data['ShowOnLoad']);
}
// Migrate all other settings
foreach($data as $key => $value) {
if($this->hasField($key)) {
$this->setField($key, $value);
}
}
}
}

View File

@ -10,8 +10,26 @@ class EditableFormHeading extends EditableFormField {
private static $singular_name = 'Heading';
private static $plural_name = 'Headings';
public function getFieldConfiguration() {
private static $db = array(
'Level' => 'Int(3)', // From CustomSettings
'HideFromReports' => 'Boolean(0)' // from CustomSettings
);
private static $defaults = array(
'Level' => 3,
'HideFromReports' => false
);
/**
* @return FieldList
*/
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeByName('Default');
$fields->removeByName('Validation');
$levels = array(
'1' => '1',
'2' => '2',
@ -21,39 +39,29 @@ class EditableFormHeading extends EditableFormField {
'6' => '6'
);
$level = ($this->getSetting('Level')) ? $this->getSetting('Level') : 3;
$label = _t('EditableFormHeading.LEVEL', 'Select Heading Level');
$options = parent::getFieldConfiguration();
$options->push(
new DropdownField($this->getSettingName("Level"), $label, $levels, $level)
);
if($this->readonly) {
$extraFields = $options->makeReadonly();
}
$options->push(
new CheckboxField(
$this->getSettingName('HideFromReports'),
_t('EditableLiteralField.HIDEFROMREPORT', 'Hide from reports?'),
$this->getSetting('HideFromReports')
$fields->addFieldsToTab('Root.Main', array(
DropdownField::create(
'Level',
_t('EditableFormHeading.LEVEL', 'Select Heading Level'),
$levels
),
CheckboxField::create(
'HideFromReports',
_t('EditableLiteralField.HIDEFROMREPORT', 'Hide from reports?')
)
);
return $options;
));
return $fields;
}
public function getFormField() {
$labelField = new HeaderField($this->Name,$this->Title, $this->getSetting('Level'));
$labelField = new HeaderField($this->Name, $this->Title, $this->Level);
$labelField->addExtraClass('FormHeading');
return $labelField;
}
public function showInReports() {
return (!$this->getSetting('HideFromReports'));
return !$this->HideFromReports;
}
public function getFieldValidationOptions() {

View File

@ -21,6 +21,15 @@ class EditableLiteralField extends EditableFormField {
*/
private static $editor_config = null;
private static $db = array(
'Content' => 'HTMLText', // From CustomSettings
'HideFromReports' => 'Boolean(0)' // from CustomSettings
);
private static $defaults = array(
'HideFromReports' => false
);
/**
* Returns the {@see HtmlEditorConfig} instance to use for sanitisation
*
@ -56,7 +65,7 @@ class EditableLiteralField extends EditableFormField {
*/
public function getContent() {
// Apply html editor sanitisation rules
$content = $this->getSetting('Content');
$content = $this->getField('Content');
return $this->sanitiseContent($content);
}
@ -68,41 +77,53 @@ class EditableLiteralField extends EditableFormField {
public function setContent($content) {
// Apply html editor sanitisation rules
$content = $this->sanitiseContent($content);
$this->setSetting('Content', $content);
$this->setField('Content', $content);
}
public function getFieldConfiguration() {
$textAreaField = new HTMLEditorField(
$this->getSettingName('Content'),
"HTML",
$this->getContent()
);
$textAreaField->setRows(4);
$textAreaField->setColumns(20);
return new FieldList(
$textAreaField,
new CheckboxField(
$this->getSettingName('HideFromReports'),
_t('EditableLiteralField.HIDEFROMREPORT', 'Hide from reports?'),
$this->getSetting('HideFromReports')
/**
* @return FieldList
*/
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeByName('Default');
$fields->removeByName('Validation');
$fields->addFieldsToTab('Root.Main', array(
HTMLEditorField::create('Content', _t('EditableLiteralField.CONTENT', 'HTML'))
->setRows(4)
->setColumns(20),
CheckboxField::create(
'HideFromReports',
_t('EditableLiteralField.HIDEFROMREPORT', 'Hide from reports?')
)
);
));
return $fields;
}
public function getFormField() {
$label = $this->Title ? "<label class='left'>$this->Title</label>":"";
$label = $this->Title
? "<label class='left'>".Convert::raw2xml($this->Title)."</label>"
: "";
$classes = $this->Title ? "" : " nolabel";
return new LiteralField("LiteralField[$this->ID]",
"<div id='$this->Name' class='field text$classes'>
$label
<div class='middleColumn literalFieldArea'>". $this->getSetting('Content') ."</div>".
"</div>"
return new LiteralField(
"LiteralField[{$this->ID}]",
sprintf(
"<div id='%s' class='field text%s'>
%s
<div class='middleColumn literalFieldArea'>%s</div>".
"</div>",
Convert::raw2htmlname($this->Name),
Convert::raw2att($classes),
$label,
$this->Content
)
);
}
public function showInReports() {
return (!$this->getSetting('HideFromReports'));
return ! $this->HideFromReports;
}
}

View File

@ -10,37 +10,46 @@ class EditableMemberListField extends EditableFormField {
private static $singular_name = 'Member List Field';
private static $plural_name = 'Member List Fields';
public function getFieldConfiguration() {
$groupID = ($this->getSetting('GroupID')) ? $this->getSetting('GroupID') : 0;
$groups = DataObject::get("Group");
private static $has_one = array(
'Group' => 'Group'
);
/**
* @return FieldList
*/
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeByName('Default');
$fields->removeByName('Validation');
if($groups) $groups = $groups->map('ID', 'Title');
$fields = new FieldList(
new DropdownField("Fields[$this->ID][CustomSettings][GroupID]", _t('EditableFormField.GROUP', 'Group'), $groups, $groupID)
$fields->addFieldToTab(
'Root.Main',
DropdownField::create(
"GroupID",
_t('EditableFormField.GROUP', 'Group'),
Group::get()->map()
)->setEmptyString(' ')
);
return $fields;
}
public function getFormField() {
if ($this->getSetting('GroupID')) {
$members = Member::map_in_groups($this->getSetting('GroupID'));
return new DropdownField($this->Name, $this->Title, $members);
if(empty($this->GroupID)) {
return false;
}
return false;
$members = Member::map_in_groups($this->GroupID);
return new DropdownField($this->Name, $this->Title, $members);
}
public function getValueFromData($data) {
if(isset($data[$this->Name])) {
$value = Convert::raw2sql($data[$this->Name]);
$member = DataObject::get_one('Member', "Member.ID = {$value}");
return ($member) ? $member->getName() : "";
$memberID = $data[$this->Name];
$member = Member::get()->byID($memberID);
return $member ? $member->getName() : "";
}
return false;

View File

@ -18,6 +18,52 @@ class EditableMultipleOptionField extends EditableFormField {
private static $has_many = array(
"Options" => "EditableOption"
);
/**
* @return FieldList
*/
public function getCMSFields() {
$fields = parent::getCMSFields();
$editableColumns = new GridFieldEditableColumns();
$editableColumns->setDisplayFields(array(
'Title' => array(
'title' => 'Title',
'callback' => function($record, $column, $grid) {
return TextField::create($column);
}
),
'Default' => array(
'title' => _t('EditableMultipleOptionField.DEFAULT', 'Selected by default?'),
'callback' => function($record, $column, $grid) {
return CheckboxField::create($column);
}
)
));
$optionsConfig = GridFieldConfig::create()
->addComponents(
new GridFieldToolbarHeader(),
new GridFieldTitleHeader(),
$editableColumns,
new GridFieldButtonRow(),
new GridFieldAddNewInlineButton(),
new GridFieldDeleteAction(),
new GridState_Component()
);
$optionsGrid = GridField::create(
'Options',
_t('EditableFormField.CUSTOMOPTIONS', 'Options'),
$this->Options(),
$optionsConfig
);
$fields->insertAfter(new Tab('Options'), 'Main');
$fields->addFieldToTab('Root.Options', $optionsGrid);
return $fields;
}
/**
* Publishing Versioning support.
@ -89,40 +135,16 @@ class EditableMultipleOptionField extends EditableFormField {
public function duplicate($doWrite = true) {
$clonedNode = parent::duplicate();
if($this->Options()) {
foreach($this->Options() as $field) {
$newField = $field->duplicate();
$newField->ParentID = $clonedNode->ID;
$newField->write();
}
foreach($this->Options() as $field) {
$newField = $field->duplicate(false);
$newField->ParentID = $clonedNode->ID;
$newField->Version = 0;
$newField->write();
}
return $clonedNode;
}
/**
* On before saving this object we need to go through and keep an eye on
* all our option fields that are related to this field in the form
*
* @param ArrayData
*/
public function populateFromPostData($data) {
parent::populateFromPostData($data);
// get the current options
$fieldSet = $this->Options();
// go over all the current options and check if ID and Title still exists
foreach($fieldSet as $option) {
if(isset($data[$option->ID]) && isset($data[$option->ID]['Title']) && $data[$option->ID]['Title'] != "field-node-deleted") {
$option->populateFromPostData($data[$option->ID]);
}
else {
$option->delete();
}
}
}
/**
* Return whether or not this field has addable options such as a
* {@link EditableDropdownField} or {@link EditableRadioField}

View File

@ -12,6 +12,11 @@ class EditableNumericField extends EditableFormField {
private static $singular_name = 'Numeric Field';
private static $plural_name = 'Numeric Fields';
private static $db = array(
'MinValue' => 'Int',
'MaxValue' => 'Int'
);
public function getSetsOwnError() {
return true;
@ -23,6 +28,7 @@ class EditableNumericField extends EditableFormField {
public function getFormField() {
$field = new NumericField($this->Name, $this->Title);
$field->addExtraClass('number');
$field->setValue($this->Default);
if ($this->Required) {
// Required and numeric validation can conflict so add the
@ -37,31 +43,25 @@ class EditableNumericField extends EditableFormField {
public function getFieldValidationOptions() {
$fields = parent::getFieldValidationOptions();
$min = ($this->getSetting('MinValue')) ? $this->getSetting('MinValue') : '';
$max = ($this->getSetting('MaxValue')) ? $this->getSetting('MaxValue') : '';
$extraFields = new FieldList(
new NumericField($this->getSettingName('MinValue'), _t('EditableFormField.MINVALUE', 'Min Value'), $min),
new NumericField($this->getSettingName('MaxValue'), _t('EditableFormField.MAXVALUE', 'Max Value'), $max)
);
$fields->merge($extraFields);
$fields->push(FieldGroup::create(
_t("EditableNumericField.RANGE", "Allowed numeric range"),
array(
new NumericField('MinValue', false),
new LiteralField('RangeValue', _t("EditableNumericField.RANGE_TO", "to")),
new NumericField('MaxValue', false)
)
));
return $fields;
}
public function getValidation() {
$options = array();
if($this->getSetting('MinValue')) {
$options['min'] = 0 + (int) $this->getSetting('MinValue');
if($this->MinValue) {
$options['min'] = (int)$this->MinValue;
}
if($this->getSetting('MaxValue')) {
$options['max'] = 0 + (int)$this->getSetting('MaxValue');
if($this->MaxValue) {
$options['max'] = (int)$this->MaxValue;
}
return $options;
}
}

View File

@ -26,23 +26,28 @@ class EditableOption extends DataObject {
"Versioned('Stage', 'Live')"
);
/**
* @param Member $member
*
* @return boolean
*/
public function canEdit($member = null) {
return ($this->Parent()->canEdit($member));
}
private static $summary_fields = array(
'Title',
'Default'
);
/**
* @param Member $member
*
* @return boolean
*/
public function canDelete($member = null) {
return ($this->Parent()->canDelete($member));
}
/**
* @param Member $member
*
* @return boolean
*/
public function canEdit($member = null) {
return ($this->Parent()->canEdit($member));
}
/**
* @param Member $member
*
* @return boolean
*/
public function canDelete($member = null) {
return ($this->Parent()->canDelete($member));
}
/**
* Template for the editing view of this option field
@ -68,20 +73,6 @@ class EditableOption extends DataObject {
public function FieldName() {
return "Fields[{$this->ParentID}][{$this->ID}]";
}
/**
* Populate this option from the form field
*
* @param Array Data
*/
public function populateFromPostData($data) {
$this->Title = (isset($data['Title'])) ? $data['Title'] : "";
$this->Default = (isset($data['Default'])) ? $data['Default'] : "";
$this->Sort = (isset($data['Sort'])) ? $data['Sort'] : 0;
$this->extend('onPopulateFromPostData', $data);
$this->write();
}
/**
* Make this option readonly
@ -91,7 +82,7 @@ class EditableOption extends DataObject {
return $this->EditSegment();
}
public function getEscapedTitle() {
return Convert::raw2att($this->Title);
}
public function getEscapedTitle() {
return Convert::raw2att($this->Title);
}
}

View File

@ -12,17 +12,35 @@ class EditableRadioField extends EditableMultipleOptionField {
private static $singular_name = 'Radio field';
private static $plural_name = 'Radio fields';
/**
* @return FieldList
*/
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeByName('Default');
return $fields;
}
public function getFormField() {
$optionSet = $this->Options();
$defaultOptions = $optionSet->filter('Default', 1);
$options = array();
if($optionSet) {
foreach( $optionSet as $option ) {
foreach($optionSet as $option) {
$options[$option->EscapedTitle] = $option->Title;
}
}
}
return new OptionsetField($this->Name, $this->Title, $options);
$field = OptionsetField::create($this->Name, $this->Title, $options);
if($defaultOptions->count()) {
$field->setValue($defaultOptions->First()->EscapedTitle);
}
return $field;
}
}

View File

@ -12,42 +12,63 @@ class EditableTextField extends EditableFormField {
private static $singular_name = 'Text Field';
private static $plural_name = 'Text Fields';
public function getFieldConfiguration() {
$fields = parent::getFieldConfiguration();
$min = ($this->getSetting('MinLength')) ? $this->getSetting('MinLength') : '';
$max = ($this->getSetting('MaxLength')) ? $this->getSetting('MaxLength') : '';
$rows = ($this->getSetting('Rows')) ? $this->getSetting('Rows') : '1';
$extraFields = new FieldList(
new FieldGroup(_t('EditableTextField.TEXTLENGTH', 'Text length'),
new NumericField($this->getSettingName('MinLength'), "", $min),
new NumericField($this->getSettingName('MaxLength'), " - ", $max)
),
new NumericField($this->getSettingName('Rows'), _t('EditableTextField.NUMBERROWS',
'Number of rows'), $rows)
);
$fields->merge($extraFields);
return $fields;
private static $db = array(
'MinLength' => 'Int',
'MaxLength' => 'Int',
'Rows' => 'Int(1)'
);
private static $defaults = array(
'Rows' => 1
);
public function getCMSFields() {
$this->beforeUpdateCMSFields(function($fields) {
$fields->addFieldToTab(
'Root.Main',
NumericField::create(
'Rows',
_t('EditableTextField.NUMBERROWS', 'Number of rows')
)->setDescription(_t(
'EditableTextField.NUMBERROWS_DESCRIPTION',
'Fields with more than one row will be generated as a textarea'
))
);
});
return parent::getCMSFields();
}
/**
* @return FieldList
*/
public function getFieldValidationOptions() {
$fields = parent::getFieldValidationOptions();
$fields->merge(array(
FieldGroup::create(
_t('EditableTextField.TEXTLENGTH', 'Allowed text length'),
array(
NumericField::create('MinLength', false),
LiteralField::create('RangeLength', _t("EditableTextField.RANGE_TO", "to")),
NumericField::create('MaxLength', false)
)
)
));
return $fields;
}
/**
* @return TextareaField|TextField
*/
public function getFormField() {
$field = NULL;
if($this->getSetting('Rows') && $this->getSetting('Rows') > 1) {
if($this->Rows > 1) {
$field = TextareaField::create($this->Name, $this->Title);
$field->setRows($this->getSetting('Rows'));
}
else {
$field = TextField::create($this->Name, $this->Title, null, $this->getSetting('MaxLength'));
$field->setRows($this->Rows);
} else {
$field = TextField::create($this->Name, $this->Title, null, $this->MaxLength);
}
if ($this->Required) {
@ -57,6 +78,8 @@ class EditableTextField extends EditableFormField {
$field->setAttribute('data-rule-required', 'true');
$field->setAttribute('data-msg-required', $errorMessage);
}
$field->setValue($this->Default);
return $field;
}
@ -71,15 +94,12 @@ class EditableTextField extends EditableFormField {
*/
public function getValidation() {
$options = parent::getValidation();
if($this->getSetting('MinLength')) {
$options['minlength'] = $this->getSetting('MinLength');
if($this->MinLength) {
$options['minlength'] = (int)$this->MinLength;
}
if($this->MaxLength) {
$options['maxlength'] = (int)$this->MaxLength;
}
if($this->getSetting('MaxLength')) {
$options['maxlength'] = $this->getSetting('MaxLength');
}
return $options;
}
}

View File

@ -0,0 +1,378 @@
<?php
/**
* A Form can have multiply members / emails to email the submission
* to and custom subjects
*
* @package userforms
*/
class UserDefinedForm_EmailRecipient extends DataObject {
private static $db = array(
'EmailAddress' => 'Varchar(200)',
'EmailSubject' => 'Varchar(200)',
'EmailFrom' => 'Varchar(200)',
'EmailReplyTo' => 'Varchar(200)',
'EmailBody' => 'Text',
'EmailBodyHtml' => 'HTMLText',
'EmailTemplate' => 'Varchar',
'SendPlain' => 'Boolean',
'HideFormData' => 'Boolean',
'CustomRulesCondition' => 'Enum("And,Or")'
);
private static $has_one = array(
'Form' => 'UserDefinedForm',
'SendEmailFromField' => 'EditableFormField',
'SendEmailToField' => 'EditableFormField',
'SendEmailSubjectField' => 'EditableFormField'
);
private static $has_many = array(
'CustomRules' => 'UserDefinedForm_EmailRecipientCondition'
);
private static $summary_fields = array(
'EmailAddress',
'EmailSubject',
'EmailFrom'
);
public function summaryFields() {
$fields = parent::summaryFields();
if(isset($fields['EmailAddress'])) {
$fields['EmailAddress'] = _t('UserDefinedForm.EMAILADDRESS', 'Email');
}
if(isset($fields['EmailSubject'])) {
$fields['EmailSubject'] = _t('UserDefinedForm.EMAILSUBJECT', 'Subject');
}
if(isset($fields['EmailFrom'])) {
$fields['EmailFrom'] = _t('UserDefinedForm.EMAILFROM', 'From');
}
return $fields;
}
/**
* Get instance of UserDefinedForm when editing in getCMSFields
*
* @return UserDefinedFrom
*/
protected function getFormParent() {
$formID = $this->FormID
? $this->FormID
: Session::get('CMSMain.currentPage');
return UserDefinedForm::get()->byID($formID);
}
public function getTitle() {
if($this->EmailAddress) {
return $this->EmailAddress;
}
if($this->EmailSubject) {
return $this->EmailSubject;
}
return parent::getTitle();
}
/**
* Generate a gridfield config for editing filter rules
*
* @return GridFieldConfig
*/
protected function getRulesConfig() {
$formFields = $this->getFormParent()->Fields();
$config = GridFieldConfig::create()
->addComponents(
new GridFieldButtonRow('before'),
new GridFieldToolbarHeader(),
new GridFieldAddNewInlineButton(),
new GridState_Component(),
new GridFieldDeleteAction(),
$columns = new GridFieldEditableColumns()
);
$columns->setDisplayFields(array(
'ConditionFieldID' => function($record, $column, $grid) use ($formFields) {
return DropdownField::create($column, false, $formFields->map('ID', 'Title'));
},
'ConditionOption' => function($record, $column, $grid) {
$options = UserDefinedForm_EmailRecipientCondition::config()->condition_options;
return DropdownField::create($column, false, $options);
},
'ConditionValue' => function($record, $column, $grid) {
return TextField::create($column);
}
));
return $config;
}
/**
* @return FieldList
*/
public function getCMSFields() {
Requirements::javascript(USERFORMS_DIR . '/javascript/Recipient.js');
// Determine optional field values
$form = $this->getFormParent();
// predefined choices are also candidates
$multiOptionFields = EditableMultipleOptionField::get()->filter('ParentID', $form->ID);
// if they have email fields then we could send from it
$validEmailFromFields = EditableEmailField::get()->filter('ParentID', $form->ID);
// For the subject, only one-line entry boxes make sense
$validSubjectFields = ArrayList::create(
EditableTextField::get()
->filter('ParentID', $form->ID)
->exclude('Rows:GreaterThan', 1)
->toArray()
);
$validSubjectFields->merge($multiOptionFields);
// To address can only be email fields or multi option fields
$validEmailToFields = new ArrayList($validEmailFromFields->toArray());
$validEmailToFields->merge($multiOptionFields);
// Build fieldlist
$fields = FieldList::create(Tabset::create('Root')->addExtraClass('EmailRecipientForm'));
// Configuration fields
$fields->addFieldsToTab('Root.EmailDetails', array(
// Subject
FieldGroup::create(
TextField::create('EmailSubject', _t('UserDefinedForm.TYPESUBJECT', 'Type subject'))
->setAttribute('style', 'min-width: 400px;'),
DropdownField::create(
'SendEmailSubjectFieldID',
_t('UserDefinedForm.SELECTAFIELDTOSETSUBJECT', '.. or select a field to use as the subject'),
$validSubjectFields->map('ID', 'Title')
)->setEmptyString('')
)
->setTitle(_t('UserDefinedForm.EMAILSUBJECT', 'Email subject')),
// To
FieldGroup::create(
TextField::create('EmailAddress', _t('UserDefinedForm.TYPETO', 'Type to address'))
->setAttribute('style', 'min-width: 400px;'),
DropdownField::create(
'SendEmailToFieldID',
_t('UserDefinedForm.ORSELECTAFIELDTOUSEASTO', '.. or select a field to use as the to address'),
$validEmailToFields->map('ID', 'Title')
)->setEmptyString(' ')
)
->setTitle(_t('UserDefinedForm.SENDEMAILTO','Send email to'))
->setDescription(_t(
'UserDefinedForm.SENDEMAILTO_DESCRIPTION',
'You may enter multiple email addresses as a comma separated list.'
)),
// From
TextField::create('EmailFrom', _t('UserDefinedForm.FROMADDRESS','Send email from'))
->setDescription(_t(
'UserDefinedForm.EmailFromContent',
"The from address allows you to set who the email comes from. On most servers this ".
"will need to be set to an email address on the same domain name as your site. ".
"For example on yoursite.com the from address may need to be something@yoursite.com. ".
"You can however, set any email address you wish as the reply to address."
)),
// Reply-To
FieldGroup::create(
TextField::create('EmailReplyTo', _t('UserDefinedForm.TYPEREPLY', 'Type reply address'))
->setAttribute('style', 'min-width: 400px;'),
DropdownField::create(
'SendEmailFromFieldID',
_t('UserDefinedForm.ORSELECTAFIELDTOUSEASFROM', '.. or select a field to use as reply to address'),
$validEmailFromFields->map('ID', 'Title')
)->setEmptyString(' ')
)
->setTitle(_t('UserDefinedForm.REPLYADDRESS', 'Email for reply to'))
->setDescription(_t(
'UserDefinedForm.REPLYADDRESS_DESCRIPTION',
'The email address which the recipient is able to \'reply\' to.'
))
));
// Only show the preview link if the recipient has been saved.
if (!empty($this->EmailTemplate)) {
$preview = sprintf(
'<p><a href="%s" target="_blank" class="ss-ui-button">%s</a></p><em>%s</em>',
"admin/pages/edit/EditForm/field/EmailRecipients/item/{$this->ID}/preview",
_t('UserDefinedForm.PREVIEW_EMAIL', 'Preview email'),
_t('UserDefinedForm.PREVIEW_EMAIL_DESCRIPTION', 'Note: Unsaved changes will not appear in the preview.')
);
} else {
$preview = sprintf(
'<em>%s</em>',
_t(
'UserDefinedForm.PREVIEW_EMAIL_UNAVAILABLE',
'You can preview this email once you have saved the Recipient.'
)
);
}
// Email templates
$fields->addFieldsToTab('Root.EmailContent', array(
CheckboxField::create('HideFormData', _t('UserDefinedForm.HIDEFORMDATA', 'Hide form data from email?')),
CheckboxField::create(
'SendPlain',
_t('UserDefinedForm.SENDPLAIN', 'Send email as plain text? (HTML will be stripped)')
),
DropdownField::create(
'EmailTemplate',
_t('UserDefinedForm.EMAILTEMPLATE', 'Email template'),
$this->getEmailTemplateDropdownValues()
)->addExtraClass('toggle-html-only'),
HTMLEditorField::create('EmailBodyHtml', _t('UserDefinedForm.EMAILBODYHTML','Body'))
->addExtraClass('toggle-html-only'),
TextareaField::create('EmailBody', _t('UserDefinedForm.EMAILBODY','Body'))
->addExtraClass('toggle-plain-only'),
LiteralField::create(
'EmailPreview',
'<div id="EmailPreview" class="field toggle-html-only">' . $preview . '</div>'
)
));
// Custom rules for sending this field
$grid = new GridField(
"CustomRules",
_t('EditableFormField.CUSTOMRULES', 'Custom Rules'),
$this->CustomRules(),
$this->getRulesConfig()
);
$grid->setDescription(_t(
'UserDefinedForm.RulesDescription',
'Emails will only be sent to the recipient if the custom rules are met. If no rules are defined, this receipient will receive notifications for every submission.'
));
$fields->addFieldsToTab('Root.CustomRules', array(
new DropdownField(
'CustomRulesCondition',
_t('UserDefinedForm.SENDIF', 'Send condition'),
array(
'Or' => 'Any conditions are true',
'And' => 'All conditions are true'
)
),
$grid
));
$this->extend('updateCMSFields', $fields);
return $fields;
}
/**
* @param Member
*
* @return boolean
*/
public function canCreate($member = null) {
return $this->Form()->canCreate();
}
/**
* @param Member
*
* @return boolean
*/
public function canView($member = null) {
return $this->Form()->canView();
}
/**
* @param Member
*
* @return boolean
*/
public function canEdit($member = null) {
return $this->Form()->canEdit();
}
/**
* @param Member
*
* @return boolean
*/
public function canDelete($member = null) {
return $this->Form()->canDelete();
}
/*
* Determine if this recipient may receive notifications for this submission
*
* @param array $data
* @param Form $form
* @return bool
*/
public function canSend($data, $form) {
// Skip if no rules configured
$customRules = $this->CustomRules();
if(!$customRules->count()) {
return true;
}
// Check all rules
$isAnd = $this->CustomRulesCondition === 'And';
foreach($customRules as $customRule) {
$matches = $customRule->matches($data, $form);
if($isAnd && !$matches) {
return false;
}
if(!$isAnd && $matches) {
return true;
}
}
// Once all rules are checked
return $isAnd;
}
/**
* Make sure the email template saved against the recipient exists on the file system.
*
* @param string
*
* @return boolean
*/
public function emailTemplateExists($template = '') {
$t = ($template ? $template : $this->EmailTemplate);
return in_array($t, $this->getEmailTemplateDropdownValues());
}
/**
* Get the email body for the current email format
*
* @return string
*/
public function getEmailBodyContent() {
return $this->SendPlain ? $this->EmailBody : $this->EmailBodyHtml;
}
/**
* Gets a list of email templates suitable for populating the email template dropdown.
*
* @return array
*/
public function getEmailTemplateDropdownValues() {
$templates = array();
$finder = new SS_FileFinder();
$finder->setOption('name_regex', '/^.*\.ss$/');
$found = $finder->find(BASE_PATH . '/' . UserDefinedForm::config()->email_template_directory);
foreach ($found as $key => $value) {
$template = pathinfo($value);
$templates[$template['filename']] = $template['filename'];
}
return $templates;
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* Declares a condition that determines whether an email can be sent to a given recipient
*/
class UserDefinedForm_EmailRecipientCondition extends DataObject {
/**
* List of options
*
* @config
* @var array
*/
private static $condition_options = array(
"IsBlank" => "Is blank",
"IsNotBlank" => "Is not blank",
"Equals" => "Equals",
"NotEquals" => "Doesn't equal"
);
private static $db = array(
'ConditionOption' => 'Enum("IsBlank,IsNotBlank,Equals,NotEquals")',
'ConditionValue' => 'Varchar'
);
private static $has_one = array(
'Parent' => 'UserDefinedForm_EmailRecipient',
'ConditionField' => 'EditableFormField'
);
/**
* Determine if this rule matches the given condition
*
* @param array $data
* @param Form $form
* @return bool
*/
public function matches($data, $form) {
$fieldName = $this->ConditionField()->Name;
$fieldValue = isset($data[$fieldName]) ? $data[$fieldName] : null;
switch($this->ConditionOption) {
case 'IsBlank':
return empty($fieldValue);
case 'IsNotBlank':
return !empty($fieldValue);
default:
$matches = is_array($fieldValue)
? in_array($this->ConditionValue, $fieldValue)
: $this->ConditionValue === (string)$fieldValue;
return ($this->ConditionOption === 'Equals') === (bool)$matches;
}
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* Email that gets sent to the people listed in the Email Recipients when a
* submission is made.
*
* @package userforms
*/
class UserFormRecipientEmail extends Email {
protected $ss_template = "SubmittedFormEmail";
protected $data;
public function __construct($submittedFields = null) {
parent::__construct($submittedFields = null);
}
/**
* Set the "Reply-To" header with an email address rather than append as
* {@link Email::replyTo} does.
*
* @param string $email The email address to set the "Reply-To" header to
*/
public function setReplyTo($email) {
$this->customHeaders['Reply-To'] = $email;
}
}

View File

@ -0,0 +1,51 @@
<?php
/**
* Controller that handles requests to EmailRecipient's
*
* @package userforms
*/
class UserFormRecipientItemRequest extends GridFieldDetailForm_ItemRequest {
private static $allowed_actions = array(
'edit',
'view',
'ItemEditForm',
'preview'
);
/**
* Renders a preview of the recipient email.
*/
public function preview() {
return $this->customise(new ArrayData(array(
'Body' => $this->record->getEmailBodyContent(),
'HideFormData' => $this->record->HideFormData,
'Fields' => $this->getPreviewFieldData()
)))->renderWith($this->record->EmailTemplate);
}
/**
* Get some placeholder field values to display in the preview
* @return ArrayList
*/
private function getPreviewFieldData() {
$data = new ArrayList();
$fields = $this->record->Form()->Fields()->filter(array(
'ClassName:not' => 'EditableLiteralField',
'ClassName:not' => 'EditableFormHeading'
));
foreach ($fields as $field) {
$data->push(new ArrayData(array(
'Name' => $field->Name,
'Title' => $field->Title,
'Value' => '$' . $field->Name,
'FormattedValue' => '$' . $field->Name
)));
}
return $data;
}
}

View File

@ -20,7 +20,7 @@ class SubmittedFileField extends SubmittedFormField {
* @return string
*/
public function getFormattedValue() {
$name = $this->getName();
$name = $this->getFileName();
$link = $this->getLink();
$title = _t('SubmittedFileField.DOWNLOADFILE', 'Download File');
@ -61,9 +61,9 @@ class SubmittedFileField extends SubmittedFormField {
*
* @return string
*/
public function getName() {
public function getFileName() {
if($this->UploadedFile()) {
return $this->UploadedFile()->Name;
}
}
}
}

View File

@ -0,0 +1,218 @@
<?php
/**
* Service to support upgrade of userforms module
*/
class UserFormsUpgradeService {
/**
* @var bool
*/
protected $quiet;
public function run() {
$this->log("Upgrading formfield rules and custom settings");
// List of rules that have been created in all stages
$fields = Versioned::get_including_deleted('EditableFormField');
foreach($fields as $field) {
$this->upgradeField($field);
}
}
/**
* Migrate a versioned field in all stages
*
* @param EditableFormField $field
*/
protected function upgradeField(EditableFormField $field) {
$this->log("Upgrading formfield ID = ".$field->ID);
// Check versions this field exists on
$filter = sprintf('"EditableFormField"."ID" = \'%d\' AND "Migrated" = 0', $field->ID);
$stageField = Versioned::get_one_by_stage('EditableFormField', 'Stage', $filter);
$liveField = Versioned::get_one_by_stage('EditableFormField', 'Live', $filter);
if($stageField) {
$this->upgradeFieldInStage($stageField, 'Stage');
}
if($liveField) {
$this->upgradeFieldInStage($liveField, 'Live');
}
}
/**
* Migrate a versioned field in a single stage
*
* @param EditableFormField $field
* @param stage $stage
*/
protected function upgradeFieldInStage(EditableFormField $field, $stage) {
Versioned::reading_stage($stage);
// Migrate field rules
$this->migrateRules($field, $stage);
// Migrate custom settings
$this->migrateCustomSettings($field, $stage);
// Flag as migrated
$field->Migrated = true;
$field->write();
}
/**
* Migrate custom rules for the given field
*
* @param EditableFormField $field
* @param string $stage
*/
protected function migrateRules(EditableFormField $field, $stage) {
$rulesData = $field->CustomRules
? unserialize($field->CustomRules)
: array();
// Skip blank rules or fields with custom rules already
if(empty($rulesData) || $field->DisplayRules()->count()) {
return;
}
// Check value of this condition
foreach($rulesData as $ruleDataItem) {
if(empty($ruleDataItem['ConditionOption']) || empty($ruleDataItem['Display'])) {
continue;
}
// Get data for this rule
$conditionOption = $ruleDataItem['ConditionOption'];
$display = $ruleDataItem['Display'];
$conditionFieldName = empty($ruleDataItem['ConditionField']) ? null : $ruleDataItem['ConditionField'];
$value = isset($ruleDataItem['Value'])
? $ruleDataItem['Value']
: null;
// Create rule
$rule = $this->findOrCreateRule($field, $stage, $conditionOption, $display, $conditionFieldName, $value);
$this->log("Upgrading rule ID = " . $rule->ID);
}
}
/**
* Migrate custom settings for the given field
*
* @param EditableFormField $field
* @param string $stage
*/
protected function migrateCustomSettings(EditableFormField $field, $stage) {
// Custom settings include:
// - ExtraClass
// - RightTitle
// - ShowOnLoad (show or '' are treated as true)
//
// - CheckedDefault (new field on EditableCheckbox - should be read from old "default" value)
// - Default (EditableCheckbox)
// - DefaultToToday (EditableDateField)
// - Folder (EditableFileField)
// - Level (EditableFormHeading)
// - HideFromReports (EditableFormHeading / EditableLiteralField)
// - Content (EditableLiteralField)
// - GroupID (EditableMemberListField)
// - MinValue (EditableNumericField)
// - MaxValue (EditableNumericField)
// - MinLength (EditableTextField)
// - MaxLength (EditableTextField)
// - Rows (EditableTextField)
$customSettings = $field->CustomSettings
? unserialize($field->CustomSettings)
: array();
// Skip blank rules or fields with custom rules already
if(empty($customSettings)) {
return;
}
$field->migrateSettings($customSettings);
$field->write();
}
/**
* Create or find an existing field with the matched specification
*
* @param EditableFormField $field
* @param string $stage
* @param string $conditionOption
* @param string $display
* @param string $conditionFieldName
* @param string $value
* @return EditableCustomRule
*/
protected function findOrCreateRule(EditableFormField $field, $stage, $conditionOption, $display, $conditionFieldName, $value) {
// Get id of field
$conditionField = $conditionFieldName
? EditableFormField::get()->filter('Name', $conditionFieldName)->first()
: null;
// If live, search stage record for matching one
if($stage === 'Live') {
$list = Versioned::get_by_stage('EditableCustomRule', 'Stage')
->filter(array(
'ParentID' => $field->ID,
'ConditionFieldID' => $conditionField ? $conditionField->ID : 0,
'Display' => $display,
'ConditionOption' => $conditionOption
));
if($value) {
$list = $list->filter('FieldValue', $value);
} else {
$list = $list->where('"FieldValue" IS NULL OR "FieldValue" = \'\'');
}
$rule = $list->first();
if($rule) {
$rule->write();
$rule->publish("Stage", "Live");
return $rule;
}
}
// If none found, or in stage, create new record
$rule = new EditableCustomRule();
$rule->ParentID = $field->ID;
$rule->ConditionFieldID = $conditionField ? $conditionField->ID : 0;
$rule->Display = $display;
$rule->ConditionOption = $conditionOption;
$rule->FieldValue = $value;
$rule->write();
return $rule;
}
protected function log($message) {
if($this->getQuiet()) {
return;
}
if(Director::is_cli()) {
echo "{$message}\n";
} else {
echo "{$message}<br />";
}
}
/**
* Set if this service should be quiet
*
* @param bool $quiet
* @return $ths
*/
public function setQuiet($quiet) {
$this->quiet = $quiet;
return $this;
}
public function getQuiet() {
return $this->quiet;
}
}

View File

@ -0,0 +1,22 @@
<?php
/**
* Assists with upgrade of userforms to 3.0
*
* @author dmooyman
*/
class UserFormsUpgradeTask extends BuildTask {
protected $title = "UserForms 3.0 Migration Tool";
protected $description = "Upgrade tool for sites upgrading to userforms 3.0";
public function run($request) {
$this->log("Upgrading userforms module");
Injector::inst()
->create('UserFormsUpgradeService')
->setQuiet(false)
->run();
$this->log("Done");
}
}

View File

@ -1,245 +0,0 @@
/**
* User Defined Form Builder CSS
*
*/
li.class-UserDefinedForm > a .jstree-pageicon { background-position: 0 -64px; }
.page-icon.class-UserDefinedForm {
background-position: 0 -64px;
}
/* Add a Field Menu
---------------------------------------- */
.MenuHolder {
color: #999;
border: 1px solid #ddd;
background: #f4f4f4;
overflow: hidden;
width: 100%;
}
.MenuHolder h2 {
float: left;
margin-left: 10px;
}
.MenuHolder select {
float: left;
width: 240px;
margin: 18px 0 0 8px;
font-size: 11px;
}
.MenuHolder .action {
float: left;
margin: 14px 0 0 4px;
font-size: 11px;
}
/* Options / Settings Area
---------------------------------------- */
.FormOptions {
padding: 10px 0 0 0;
}
/*
#ShowClearButton, #DisableSaveSubmissions{
padding-left:0;
}
#ShowClearButton label, #DisableSaveSubmissions label{
float:left;
font-weight:bold;
margin-right:43px;
}
*/
/* Email Recipient Form
---------------------------------------- */
.EmailRecipientForm .fieldgroup .fieldgroup-field {
padding-top: 0;
}
.EmailRecipientForm .fieldgroup .fieldgroup-field.last {
padding-bottom: 0;
}
.EmailRecipientForm .ss-ui-button {
margin-bottom: 4px;
}
/* Field Listing
---------------------------------------- */
.FieldEditor .FieldList {
padding: 10px 0;
}
.FieldEditor .FieldList .EditableFormField {
margin: 2px;
padding: 3px 0;
border-top: 1px dotted #ccc;
width: 100%;
overflow: hidden;
}
.FieldEditor .FieldList .EditableFormField * {
vertical-align: middle;
}
.FieldEditor .FieldList .EditableFormField .fieldHandler,
.FieldEditor .FieldList .EditableFormField .handle {
cursor: move;
}
.FieldEditor .FieldList .EditableFormField .fieldInfo {
float: left;
padding-right: 10px;
}
.FieldEditor .FieldList .EditableFormField .fieldActions {
float: left;
margin-top: 10px;
}
.FieldEditor .FieldList .EditableFormField .fieldActions a {
padding: 0 5px 5px 20px;
font-size: 12px;
}
.FieldEditor .FieldList .EditableFormField .moreOptions {
background: url(../../cms/images/edit.gif) no-repeat top left;
}
.FieldEditor .FieldList .EditableFormField .moreOptions.showing {
color: #666;
text-decoration: none;
}
.FieldEditor .FieldList .EditableFormField .delete {
background: url(../../cms/images/delete.gif) no-repeat top left;
}
.FieldEditor .FieldList .EditableFormField input {
width: 250px;
margin-left: 0px;
}
/** Field Options **/
.FieldEditor .FieldList .EditableFormField div.extraOptions {
display: block;
overflow:hidden;
margin: 3px 0px 3px 38px;
background-color: #F4F4F4;
padding: 3px;
clear: both;
}
.FieldEditor .FieldList .EditableFormField .extraOptions .handle {
float: left;
margin-right: 3px;
}
.FieldEditor .FieldList .EditableFormField .extraOptions ul {
padding: 5px 0;
}
.FieldEditor .FieldList .EditableFormField .extraOptions li {
padding: 3px 0;
width: 100%;
overflow: hidden;
}
.FieldEditor .FieldList .EditableFormField .extraOptions a {
background: none;
}
.FieldEditor .FieldList .EditableFormField .extraOptions input {
font-size: 11px;
padding: 2px;
float: left;
}
.FieldEditor .FieldList .EditableFormField .extraOptions .deleteOption {
width: 20px;
margin: 4px 0 0 4px;
background: none;
height: 20px;
float: left;
display: block;
}
.FieldEditor .FieldList .EditableFormField .extraOptions select {
width: 221px;
}
.FieldEditor .FieldList .EditableFormField .extraOptions .chzn-drop input {
font-size: 12px;
padding: 4px 20px 4px 5px;
float: none;
}
.FieldEditor .FieldList .EditableFormField a.addableOption,
.FieldEditor .FieldList .EditableFormField a.addCondition {
background: url(../../cms/images/add.gif) no-repeat top left;
padding: 1px 0 2px 20px;
font-size: 12px;
width: auto;
margin-left: 3px;
}
/* Field Options Group */
.FieldEditor .FieldList .fieldOptionsGroup {
padding: 4px 8px 8px 8px;
margin: 5px;
border: 1px solid #bbb;
}
.FieldEditor .FieldList .fieldOptionsGroup legend {
font-size: 15px;
padding: 0 4px;
}
/* Field Lengths */
.FieldEditor .FieldList .EditableFormField .fieldgroupField {
float: left;
}
.FieldEditor .FieldList .EditableFormField .fieldgroupField label {
float: left;
padding: 0 4px;
}
.FieldEditor .FieldList .EditableFormField .fieldgroupField input {
width: 80px;
}
.FieldEditor .FieldList .EditableFormField .middleColumn {
background: none;
}
/* CHECKBOX */
.FieldEditor .FieldList .EditableFormField .checkbox { }
.FieldEditor .FieldList .EditableFormField .checkbox input {
float: left;
width:auto;
margin: 0 8px 0 0;
}
/* CUSTOM RULES */
.FieldEditor .FieldList .customRules {
clear: both;
}
.FieldEditor .FieldList .customRules li {
clear: both;
}
.FieldEditor .FieldList .customRules li.firstField {
padding-bottom: 5px;
margin: 0 5px;
border-bottom: 1px solid #bbb;
}
.FieldEditor .FieldList .customRules label {
float: left;
margin: 0;
padding: 2px 4px;
font-size: 11px;
}
.FieldEditor .FieldList .customRules select {
float: left;
font-size: 11px;
width: 120px;
margin-right: 4px;
}
.FieldEditor .FieldList .customRules a {
background: none;
width: 20px;
float: left;
}
/* HIDE */
.FieldEditor .FieldList li.EditableFormField .hidden {
display: none !important;
}
/* Holder to prevent form from collapsing */
.FieldEditor .FieldList .removed-form-field {
height: 40px;
display: block;
overflow: hidden;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -53,8 +53,8 @@ puts the form at the end of all the content.
## Adding fields
To add a field to the form, click on the "Form" tab under the "Content" tab in the
Editing Pane. Select the type of field you want to add from the drop-down menu and
press the "Add" button. You can label any field by typing in the appropriate label field in the backend.
Editing Pane. Click the "Add" button then select the type of field you want from the dropdown.
Save or publish the form to start editing your new field's properties.
![Adding fields](_images/add-fields.png)

View File

@ -8,19 +8,12 @@
var recipient = {
// Some fields are only visible when HTML email are being sent.
updateFormatSpecificFields: function () {
var sendPlainChecked = $('#SendPlain').find('input[type="checkbox"]').is(':checked');
var sendPlainChecked = $('#SendPlain')
.find('input[type="checkbox"]')
.is(':checked');
// Hide the preview link when 'SendPlain' is selected.
$('#EmailPreview')[sendPlainChecked ? 'hide' : 'show']();
// Hide the template selector when 'SendPlain' is selected.
$('#EmailTemplate')[sendPlainChecked ? 'hide' : 'show']();
// Hide the HTML editor when 'SendPlain' is selected.
$('#EmailBodyHtml')[sendPlainChecked ? 'hide' : 'show']();
// Show the body teaxtarea when 'SendPlain' is selected.
$('#EmailBody')[sendPlainChecked ? 'show' : 'hide']();
$(".field.toggle-html-only")[sendPlainChecked ? 'hide' : 'show']();
$(".field.toggle-plain-only")[sendPlainChecked ? 'show' : 'hide']();
}
};

View File

@ -1,353 +0,0 @@
/**
* Javascript required to power the user defined forms.
*
* Rewritten from the prototype FieldEditor and constantly
* being refactored to be be less specific on the UDF dom.
*/
(function($) {
$(document).ready(function() {
/**
* Namespace
*/
var userforms = userforms || {};
/**
* Messages from UserForms are translatable using i18n.
*/
userforms.messages = {
CONFIRM_DELETE_ALL_SUBMISSIONS: 'All submissions will be permanently removed. Continue?',
ERROR_CREATING_FIELD: 'Error creating field',
ADDING_FIELD: 'Adding new field',
ADDED_FIELD: 'Added new field',
HIDE_OPTIONS: 'Hide options',
SHOW_OPTIONS: 'Show options',
ADDING_OPTION: 'Adding option',
ADDED_OPTION: 'Added option',
ERROR_CREATING_OPTION: 'Error creating option',
REMOVED_OPTION: 'Removed option',
ADDING_RULE: 'Adding rule'
};
/**
* Returns a given translatable string from a passed key. Keys
* should be all caps without any spaces.
*/
userforms.message = function() {
en = arguments[1] || userforms.messages[arguments[0]];
return ss.i18n._t("UserForms."+ arguments[0], en);
};
/**
* Update the sortable properties of the form as a function
* since the application will need to refresh the UI dynamically based
* on a number of factors including when the user adds a page or
* swaps between pages
*
*/
userforms.update = function() {
$("#Fields_fields").sortable({
handle: '.fieldHandler',
cursor: 'pointer',
items: 'li.EditableFormField',
placeholder: 'removed-form-field',
opacity: 0.6,
revert: 'true',
change : function (event, ui) {
$("#Fields_fields").sortable('refreshPositions');
},
update : function (event, ui) {
var sort = 1;
$("li.EditableFormField").each(function() {
$(this).find(".sortHidden").val(sort++);
});
}
});
$(".editableOptions").sortable({
handle: '.handle',
cursor:'pointer',
items: 'li',
placeholder: 'removed-form-field',
opacity: 0.6,
revert: true,
change : function (event, ui) {
$(this).sortable('refreshPositions');
},
update : function (event, ui) {
var sort = 1;
$(".editableOptions li").each(function() {
$(this).find(".sortOptionHidden").val(sort++);
});
}
});
};
userforms.appendToURL = function(url, pathsegmenttobeadded) {
var parts = url.match(/([^\?#]*)?(\?[^#]*)?(#.*)?/);
for(var i in parts) if(!parts[i]) parts[i] = '';
return parts[1] + pathsegmenttobeadded + parts[2] + parts[3];
}
/**
* Workaround for not refreshing the sort.
*
* TODO: better solution would to not fire this on every hover but needs to
* ensure it doesn't have edge cases. The sledge hammer approach.
*/
$(".fieldHandler, .handle").live('hover', function() {
userforms.update();
});
/**
* Kick off the UserForms UI
*/
userforms.update();
$.entwine('udf', function($){
/*-------------------- FIELD EDITOR ----------------------- */
/**
* Create a new instance of a field in the current form
* area. the type information should all be on this object
*/
$('div.FieldEditor .MenuHolder .action').entwine({
onclick: function(e) {
var theform = $("#Form_EditForm"); // edit from page
if ( theform.length < 1 ) theform = $("#Form_ItemEditForm"); // edit from modeladmin
var form = theform,
length = $(".FieldInfo").length + 1,
fieldType = $(this).siblings("select").val(),
formData = form.serialize()+'&NewID='+ length +"&Type="+ fieldType,
fieldEditor = $(this).closest('.FieldEditor');
e.preventDefault();
if($("#Fields").hasClass('readonly') || !fieldType) {
return;
}
// Due to some very weird behaviout of jquery.metadata, the url have to be double quoted
var addURL = fieldEditor.attr('data-add-url').substr(1, fieldEditor.attr('data-add-url').length-2);
$.ajax({
headers: {"X-Pjax" : 'Partial'},
type: "POST",
url: addURL,
data: formData,
success: function(data) {
$('#Fields_fields').append(data);
statusMessage(userforms.message('ADDED_FIELD'));
var name = $("#Fields_fields li.EditableFormField:last").attr("id").split(' ');
$("#Fields_fields select.fieldOption").append("<option value='"+ name[2] +"'>New "+ name[2] + "</option>");
$("#Fields_fields").sortable('refresh');
},
error: function(e) {
alert(ss.i18n._t('GRIDFIELD.ERRORINTRANSACTION'));
}
});
}
});
/**
* Delete a field from the user defined form
*/
$(".EditableFormField .delete").entwine({
onclick: function(e) {
e.preventDefault();
var text = $(this).parents("li").find(".fieldInfo .text").val();
// Remove all the rules with relate to this field
$("#Fields_fields .customRules select.fieldOption option").each(function(i, element) {
if($(element).text() === text) {
// check to see if this is selected. If it is then just remove the whole rule
if($(element).parent('select.customRuleField').val() === $(element).val()) {
$(element).parents('li.customRule').remove();
} else {
// otherwise remove the option
$(element).remove();
}
}
});
$(this).parents(".EditableFormField").slideUp(function(){$(this).remove()})
}
});
/**
* Upon renaming a field we should go through and rename all the
* fields in the select fields to use this new field title. We can
* just worry about the title text - don't mess around with the keys
*/
$('.EditableFormField .fieldInfo .text').entwine({
onchange: function(e) {
var value = $(this).val();
var name = $(this).parents("li").attr("id").split(' ');
$("#Fields_fields select.fieldOption option").each(function(i, domElement) {
if($(domElement).val() === name[2]) {
$(domElement).text(value);
}
});
}
});
/**
* Show the more options popdown. Or hide it if we currently have it open
*/
$(".EditableFormField .moreOptions").entwine({
onclick: function(e) {
e.preventDefault();
var parent = $(this).parents(".EditableFormField");
if(!parent) {
return;
}
var extraOptions = parent.children(".extraOptions");
if(!extraOptions) {
return;
}
if(extraOptions.hasClass('hidden')) {
$(this).addClass("showing");
$(this).html('Hide options');
extraOptions.removeClass('hidden');
} else {
$(this).removeClass("showing");
$(this).html('Show options');
extraOptions.addClass('hidden');
}
}
});
/**
* Add a suboption to a radio field or to a dropdown box for example
*/
$(".EditableFormField .addableOption").entwine({
onclick: function(e) {
e.preventDefault();
// Give the user some feedback
statusMessage(userforms.message('ADDING_OPTION'));
// variables
var options = $(this).parent("li");
var action = userforms.appendToURL($("#Form_EditForm").attr("action"), '/field/Fields/addoptionfield');
var parent = $(this).attr("rel");
var securityID = ($("input[name=SecurityID]").length > 0) ? $("input[name=SecurityID]").first().attr("value") : '';
// send ajax request to the page
$.ajax({
type: "GET",
url: action,
data: 'Parent='+ parent +'&SecurityID='+securityID,
// create a new field
success: function(msg){
options.before(msg);
statusMessage(userforms.message('ADDED_OPTION'));
},
// error creating new field
error: function(request, text, error) {
statusMessage(userforms.message('ERROR_CREATING_OPTION'));
}
});
}
});
/**
* Delete a suboption such as an dropdown option or a
* checkbox field
*/
$(".EditableFormField .deleteOption").entwine({
onclick: function(e) {
e.preventDefault();
// pass the deleted status onto the element
$(this).parents("li:first").find("[type=text]:first").attr("value", "field-node-deleted");
$(this).parents("li:first").hide();
// Give the user some feedback
statusMessage(userforms.message('REMOVED_OPTION'));
}
});
/**
* Custom Rules Interface
*
* Hides the input text field if the conditionOption is 'IsBlank' or 'IsNotBlank'
*/
$("select.conditionOption").entwine({
onchange: function() {
var valueInput = $(this).siblings(".ruleValue");
if($(this).val() && $(this).val() !== "IsBlank" && $(this).val() !== "IsNotBlank") {
valueInput.removeClass("hidden");
} else {
valueInput.addClass("hidden");
}
}
});
/**
* Delete a custom rule
*/
$(".customRules .deleteCondition").entwine({
onclick: function(e) {
e.preventDefault();
$(this).parent("li").fadeOut().remove();
}
});
/**
* Adding a custom rule to a given form
*/
$(".customRules .addCondition").entwine({
onclick: function(e) {
e.preventDefault();
// Give the user some feedback
statusMessage(userforms.message('ADDING_RULE'));
// get the fields li which to duplicate
var currentRules = $(this).parent("li").parent("ul");
var defaultRule = currentRules.children("li.hidden:first");
var newRule = defaultRule.clone();
newRule.children(".customRuleField").each(function(i, domElement) {
var currentName = domElement.name.split("][");
currentName[3] = currentName[2];
currentName[2] = currentRules.children().size() + 1;
domElement.name = currentName.join("][");
});
// remove hidden tag
newRule.removeClass("hidden");
// update the fields dropdown
var optionChildren = newRule.children("select.fieldOption");
optionChildren.empty();
$("#Fields_fields li.EditableFormField").each(function () {
var name = $(this).attr("id").split(' ');
var option = $("<option></option>")
.attr('value', name[2])
.text($(this).find(".text").val());
optionChildren
.append(option);
});
// append to the list
currentRules.append(newRule);
}
});
});
});
})(jQuery);

View File

@ -1,134 +0,0 @@
<!-- 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">
<div class="fieldInfo">
<% if canEdit %>
<img class="fieldHandler" src="$ModulePath(framework)/images/drag.gif" alt="<% _t('EditableFormField.DRAG', 'Drag to rearrange order of fields') %>" />
<% else %>
<img class="fieldHandler" src="$ModulePath(framework)/images/drag_readonly.gif" alt="<% _t('EditableFormField.LOCKED', 'These fields cannot be modified') %>" />
<% end_if %>
<img class="icon" src="$Icon" alt="$ClassName" title="$singular_name" />
$TitleField
</div>
<div class="fieldActions">
<% if showExtraOptions %>
<a class="moreOptions" href="#" title="<% _t('EditableFormField.SHOWOPTIONS', 'Show Options') %>"><% _t('EditableFormField.SHOWOPTIONS','Show Options') %></a>
<% end_if %>
<% if canDelete %>
<a class="delete" href="#" title="<% _t('EditableFormField.DELETE', 'Delete') %>"><% _t('EditableFormField.DELETE', 'Delete') %></a>
<% end_if %>
</div>
<% if showExtraOptions %>
<div class="extraOptions hidden" id="$Name.ATT-extraOptions">
<% if HasAddableOptions %>
<fieldset class="fieldOptionsGroup">
<legend><% _t('EditableFormField.OPTIONS', 'Options') %></legend>
<ul class="editableOptions" id="$FieldName.ATT-list">
<% if canEdit %>
<% loop Options %>
$EditSegment
<% end_loop %>
<% if HasAddableOptions %>
<li class="{$ClassName}Option">
<a href="#" rel="$ID" class="addableOption" title="<% _t('EditableFormField.ADD', 'Add option to field') %>">
<% _t('EditableFormField.ADDLabel', 'Add option') %>
</a>
</li>
<% end_if %>
<% else %>
<% loop Options %>
$ReadonlyOption
<% end_loop %>
<% end_if %>
</ul>
</fieldset>
<% end_if %>
<% if FieldConfiguration %>
<fieldset class="fieldOptionsGroup">
<legend><% _t('EditableFormField.FIELDCONFIGURATION', 'Field Configuration') %></legend>
<% loop FieldConfiguration %>
$FieldHolder
<% end_loop %>
</fieldset>
<% end_if %>
<% if FieldValidationOptions %>
<fieldset class="fieldOptionsGroup">
<legend><% _t('EditableFormField.VALIDATION', 'Validation') %></legend>
<% loop FieldValidationOptions %>
$FieldHolder
<% end_loop %>
</fieldset>
<% end_if %>
<fieldset class="customRules fieldOptionsGroup">
<legend><% _t('EditableFormField.CUSTOMRULES', 'Custom Rules') %></legend>
<ul id="{$FieldName}-customRules">
<li>
<a href="#" class="addCondition" title="<% _t('EditableFormField.ADD', 'Add') %>"><% _t('EditableFormField.ADDRULE', 'Add Rule') %></a>
</li>
<li class="addCustomRule">
<select name="{$FieldName}[CustomSettings][ShowOnLoad]">
<option value="Show" <% if ShowOnLoad %>selected="selected"<% end_if %>><% _t('EditableFormField.SHOW', 'Show') %></option>
<option value="Hide" <% if ShowOnLoad %><% else %>selected="selected"<% end_if %>><% _t('EditableFormField.HIDE', 'Hide') %></option>
</select>
<label class="left"><% _t('EditableFormField.FIELDONDEFAULT', 'Field On Default') %></label>
</li>
<li class="hidden">
<select class="displayOption customRuleField" name="{$FieldName}[CustomRules][Display]">
<option value="Show"><% _t('EditableFormField.SHOWTHISFIELD', 'Show This Field') %></option>
<option value="Hide"><% _t('EditableFormField.HIDETHISFIELD', 'Hide This Field') %></option>
</select>
<label><% _t('EditableFormField.WHEN', 'When') %></label>
<select class="fieldOption customRuleField" name="{$FieldName}[CustomRules][ConditionField]">
<option></option>
<% loop Parent %>
<% if Fields %>
<% loop Fields %>
<option value="$Name"><% if Title %>$Title<% else %>$Name<% end_if %></option>
<% end_loop %>
<% end_if %>
<% end_loop %>
</select>
<label><% _t('EditableFormField.IS', 'Is') %></label>
<select class="conditionOption customRuleField" name="{$FieldName}[CustomRules][ConditionOption]">
<option value=""></option>
<option value="IsBlank"><% _t('EditableFormField.BLANK', 'Blank') %></option>
<option value="IsNotBlank"><% _t('EditableFormField.NOTBLANK', 'Not Blank') %></option>
<option value="HasValue"><% _t('EditableFormField.VALUE', 'Value') %></option>
<option value="ValueNot"><% _t('EditableFormField.NOTVALUE', 'Not Value') %></option>
<option value="ValueLessThan"><% _t('EditableFormField.LESSTHAN', 'Value Less Than') %></option>
<option value="ValueLessThanEqual"><% _t('EditableFormField.LESSTHANEQUAL', 'Value Less Than Or Equal') %></option>
<option value="ValueGreaterThan"><% _t('EditableFormField.GREATERTHAN', 'Value Greater Than') %></option>
<option value="ValueGreaterThanEqual"><% _t('EditableFormField.GREATERTHANEQUAL', 'Value Greater Than Or Equal') %></option>
</select>
<input type="text" class="ruleValue hidden customRuleField" name="{$FieldName}[CustomRules][Value]" />
<a href="#" class="deleteCondition" title="<% _t('EditableFormField.DELETE', 'Delete') %>"><img src="cms/images/delete.gif" alt="<% _t('EditableFormField.DELETE', 'Delete') %>" /></a>
</li>
<% if CustomRules %>
<% loop CustomRules %>
<li>
<% include CustomRule %>
</li>
<% end_loop %>
<% end_if %>
</ul>
</fieldset>
</div>
<% end_if %>
<!-- Hidden option Fields -->
<input type="hidden" class="typeHidden" name="{$FieldName}[Type]" value="$ClassName" />
<input type="hidden" class="sortHidden" name="{$FieldName}[Sort]" value="$Sort" />
</li>

View File

@ -1,11 +0,0 @@
<li>
<img class="handle" src="$ModulePath(framework)/images/drag.gif" alt="<% _t('EditableOption.DRAG', 'Drag to rearrange order of options') %>" />
<input type="text" name="{$FieldName}[Title]" value="$Title" />
<input type="hidden" class="sortOptionHidden hidden" name="{$FieldName}[Sort]" value="$Sort" />
<% if canEdit %>
<a href="$ID" class="deleteOption"><img src="$ModulePath(framework)/images/delete.gif" alt="<% _t('EditableOption.DELETE', 'Remove this option') %>" /></a>
<% else %>
<img src="cms/images/locked.gif" alt="<% _t('EditableOption.LOCKED', 'These fields cannot be modified') %>" />
<% end_if %>
</li>

View File

@ -1,31 +0,0 @@
<% require css(userforms/css/FieldEditor.css) %>
<% require javascript(userforms/javascript/UserForm.js) %>
<div class="FieldEditor <% if canEdit %><% else %>readonly<% end_if %>" id="Fields" $AttributesHTML>
<div class="FieldListHold">
<ul class="FieldList" id="Fields_fields">
<% loop Fields %>
$EditSegment
<% end_loop %>
</ul>
</div>
<% if canEdit %>
<div class="MenuHolder no-change-track">
<h2><% _t('FieldEditor.ADD', 'Add') %></h2>
<select name="AddUserFormField" id="AddUserFormField">
<option value=""><% _t('FieldEditor.SELECTAFIELD', 'Select a Field') %></option>
<% loop CreatableFields %>
<option value="$ClassName">$Title</option>
<% end_loop %>
</select>
<input type="hidden" name="SecurityID" value="$SecurityID" />
<input type="submit" class="action" value="<% _t('FieldEditor.ADD', 'Add') %>" />
</div>
<% end_if %>
</div>

View File

@ -1,28 +0,0 @@
<select class="displayOption customRuleField" name="{$FieldName}[CustomRules][$Pos][Display]">
<option value="Show" <% if Display = Show %>selected="selected"<% end_if %>><% _t('CustomRule.SHOWTHISFIELD', 'Show This Field') %></option>
<option value="Hide" <% if Display = Hide %>selected="selected"<% end_if %>><% _t('CustomRule.HIDETHISFIELD', 'Hide This Field') %></option>
</select>
<label><% _t('CustomRule.WHEN', 'When') %></label>
<select class="fieldOption customRuleField" name="{$FieldName}[CustomRules][$Pos][ConditionField]">
<option value="" selected="selected"></option>
<% loop Fields %>
<option value="$Name" <% if isSelected %>selected="selected"<% end_if %>>$Title</option>
<% end_loop %>
</select>
<label><% _t('CustomRule.IS', 'Is') %></label>
<select class="conditionOption customRuleField" name="{$FieldName}[CustomRules][$Pos][ConditionOption]">
<option value="IsBlank" <% if ConditionOption = IsBlank %>selected="selected"<% end_if %>><% _t('CustomRule.BLANK', 'Blank') %></option>
<option value="IsNotBlank" <% if ConditionOption = IsNotBlank %>selected="selected"<% end_if %>><% _t('CustomRule.NOTBLANK', 'Not Blank') %></option>
<option value="HasValue" <% if ConditionOption = HasValue %>selected="selected"<% end_if %>><% _t('CustomRule.VALUE', 'Value') %></option>
<option value="ValueNot" <% if ConditionOption = ValueNot %>selected="selected"<% end_if %>><% _t('CustomRule.NOTVALUE', 'Not Value') %></option>
<option value="ValueLessThan" <% if ConditionOption = ValueLessThan %>selected="selected"<% end_if %>><% _t('CustomRule.LESSTHAN', 'Value Less Than') %></option>
<option value="ValueLessThanEqual" <% if ConditionOption = ValueLessThanEqual %>selected="selected"<% end_if %>><% _t('CustomRule.LESSTHANEQUAL', 'Value Less Than Or Equal') %></option>
<option value="ValueGreaterThan" <% if ConditionOption = ValueGreaterThan %>selected="selected"<% end_if %>><% _t('CustomRule.GREATERTHAN', 'Value Greater Than') %></option>
<option value="ValueGreaterThanEqual" <% if ConditionOption = ValueGreaterThanEqual %>selected="selected"<% end_if %>><% _t('CustomRule.GREATERTHANEQUAL', 'Value Greater Than Or Equal') %></option>
</select>
<input type="text" class="ruleValue <% if ConditionOption %><% if ConditionOption = IsBlank %>hidden<% else_if ConditionOption = IsNotBlank %>hidden<% end_if %><% else %>hidden<% end_if %> customRuleField" name="{$FieldName}[CustomRules][$Pos][Value]" value="$Value" />
<a href="#" class="deleteCondition" title="<% _t('CustomRule.DELETE', 'Delete') %>"><img src="cms/images/delete.gif" alt="<% _t('CustomRule.DELETE', 'Delete') %>" /></a>

View File

@ -7,11 +7,6 @@
class EditableFormFieldTest extends FunctionalTest {
static $fixture_file = 'userforms/tests/EditableFormFieldTest.yml';
protected $extraDataObjects = array(
'ExtendedEditableFormFieldTestOnly',
'EditableFormFieldExtensionTestOnly'
);
function testFormFieldPermissions() {
$text = $this->objFromFixture('EditableTextField', 'basic-text');
@ -41,100 +36,6 @@ class EditableFormFieldTest extends FunctionalTest {
$this->assertFalse($text->canDelete());
}
function testGettingAndSettingSettings() {
$text = $this->objFromFixture('EditableTextField', 'basic-text');
$this->logInWithPermission('ADMIN');
$this->assertEquals($text->getSettings(), array());
$text->setSetting('Test', 'Value');
$text->write();
$this->assertEquals($text->getSetting('Test'), 'Value');
$this->assertEquals($text->getSettings(), array('Test' => 'Value'));
$text->setSetting('Foo', 'Bar');
$text->write();
$this->assertEquals($text->getSetting('Foo'), 'Bar');
$this->assertEquals($text->getSettings(), array('Test' => 'Value', 'Foo' => 'Bar'));
// test overridding an existing setting
$text->setSetting('Foo', 'Baz');
$text->write();
$this->assertEquals($text->getSetting('Foo'), 'Baz');
$this->assertEquals($text->getSettings(), array('Test' => 'Value', 'Foo' => 'Baz'));
}
function testShowOnLoad() {
$text = $this->objFromFixture('EditableTextField', 'basic-text');
$this->logInWithPermission('ADMIN');
$this->assertTrue($text->getShowOnLoad());
$text->setSetting('ShowOnLoad', 'Show');
$this->assertTrue($text->getShowOnLoad());
$text->setSetting('ShowOnLoad', 'Hide');
$this->assertFalse($text->getShowOnLoad());
$text->setSetting('ShowOnLoad', '');
$this->assertTrue($text->getShowOnLoad());
}
function testPopulateFromPostData() {
$this->logInWithPermission('ADMIN');
$set = new ArrayList();
$field = new EditableFormField();
$data = array(
'Title' => 'Field Title',
'Default' => 'Default Value',
'Sort' => '2',
'Required' => 0,
'CustomErrorMessage' => 'Custom'
);
$field->populateFromPostData($data);
$set->push($field);
$this->assertDOSEquals(array($data), $set);
// test the custom settings
$data['CustomSettings'] = array(
'Foo' => 'Bar'
);
$checkbox = new EditableCheckbox();
$checkbox->write();
$checkbox->populateFromPostData(array('Title' => 'Checkbox'));
$field->populateFromPostData($data);
$this->assertEquals($field->getSettings(), array('Foo' => 'Bar'));
$rule = array(
'Display' => 'Hide',
'ConditionField' => $checkbox->Name,
'ConditionOption' => 'HasValue',
'Value' => 6
);
// test the custom rules
$data['CustomRules'] = array(
'Rule1' => $rule
);
$field->populateFromPostData($data);
$rules = unserialize($field->CustomRules);
$this->assertEquals($rules[0], $rule);
}
function testCustomRules() {
$this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'custom-rules-form');
@ -142,58 +43,18 @@ class EditableFormFieldTest extends FunctionalTest {
$checkbox = $form->Fields()->find('ClassName', 'EditableCheckbox');
$field = $form->Fields()->find('ClassName', 'EditableTextField');
$rule = array(
'Display' => 'Hide',
'ConditionField' => $checkbox->Name,
'ConditionOption' => 'HasValue',
'Value' => 6
);
$rules = $checkbox->DisplayRules();
$data['CustomRules'] = array(
'Rule1' => $rule
);
$field->populateFromPostData($data);
$rules = $field->CustomRules();
// form has 2 fields - a checkbox and a text field
// it has 1 rule - when ticked the checkbox hides the text field
$this->assertEquals($rules->Count(), 1);
// rules are ArrayDatas not dataobjects
// $this->assertDOSEquals(array($rule), $rules);
$checkboxRule = $rules->First();
$checkboxRule->ConditionFieldID = $field->ID;
$this->assertEquals($checkboxRule->Display, 'Hide');
$this->assertEquals($checkboxRule->ConditionField, $checkbox->Name);
$this->assertEquals($checkboxRule->ConditionOption, 'HasValue');
$this->assertEquals($checkboxRule->Value, '6');
foreach($checkboxRule->Fields as $condition) {
if($checkbox->Name == $condition->Name) {
$this->assertTrue($condition->isSelected);
}
else {
$this->assertFalse($condition->isSelected);
}
}
$data['CustomRules'] = array(
'Rule2' => array(
'Display' => 'Hide',
'ConditionField' => $checkbox->Name,
'ConditionOption' => 'Blank'
)
);
$field->populateFromPostData($data);
$rules = $field->CustomRules();
// test that saving additional rules deletes the old one
$this->assertEquals($rules->Count(), 1);
$this->assertEquals($checkboxRule->FieldValue, '6');
}
function testEditableDropdownField() {
@ -240,15 +101,6 @@ class EditableFormFieldTest extends FunctionalTest {
$this->assertEquals($title->Value(), "Basic Text Field");
}
function testGettingFieldAndSettingNames() {
$text = $this->objFromFixture('EditableTextField', 'basic-text');
$this->assertEquals($text->getFieldName(), "Fields[". $text->ID ."]");
$this->assertEquals($text->getFieldName('Setting'), "Fields[". $text->ID ."][Setting]");
$this->assertEquals($text->getSettingName('Foo'), "Fields[". $text->ID ."][CustomSettings][Foo]");
}
function testMultipleOptionDuplication() {
$dropdown = $this->objFromFixture('EditableDropdown','basic-dropdown');
@ -262,70 +114,6 @@ class EditableFormFieldTest extends FunctionalTest {
$this->assertEquals($orginal->Sort, $option->Sort);
}
}
function testMultipleOptionPopulateFromPostData() {
$dropdown = $this->objFromFixture('EditableDropdown','basic-dropdown');
$data = array();
foreach($dropdown->Options() as $option) {
$orginal[$option->ID] = array(
'Title' => $option->Title,
'Sort' => $option->Sort
);
$data[$option->ID] = array(
'Title' => 'New - '. $option->Title,
'Sort' => $option->Sort + 1
);
}
$dropdown->populateFromPostData($data);
$count = $dropdown->Options()->Count();
foreach($dropdown->Options() as $option) {
$this->assertEquals($option->Title, 'New - '. $orginal[$option->ID]['Title']);
$this->assertEquals($option->Sort, $orginal[$option->ID]['Sort'] + 1);
}
// remove the first one. can't assume by ID
foreach($data as $key => $value) {
unset($data[$key]);
break;
}
$dropdown->populateFromPostData($data);
$this->assertEquals($dropdown->Options()->Count(), $count-1);
}
function testEditableTextFieldConfiguration() {
// $text = $this->objFromFixture('EditableTextField', 'basic-text');
// $configuration = $text->getFieldConfiguration();
}
function testExtendedEditableFormField() {
/** @var ExtendedEditableFormField $field */
$field = $this->objFromFixture('ExtendedEditableFormFieldTestOnly', 'extended-field');
// Check db fields
$dbFields = $field->stat('db');
$this->assertTrue(array_key_exists('TestExtraField', $dbFields));
$this->assertTrue(array_key_exists('TestValidationField', $dbFields));
// Check Field Configuration
$fieldConfiguration = $field->getFieldConfiguration();
$extraField = $fieldConfiguration->dataFieldByName($field->getSettingName('TestExtraField'));
$this->assertNotNull($extraField);
// Check Validation Fields
$fieldValidation = $field->getFieldValidationOptions();
$validationField = $fieldValidation->dataFieldByName($field->getSettingName('TestValidationField'));
$this->assertNotNull($validationField);
}
public function testFileField() {
$fileField = $this->objFromFixture('EditableFileField', 'file-field');
@ -336,48 +124,3 @@ class EditableFormFieldTest extends FunctionalTest {
}
}
/**
* Class ExtendedEditableFormField
* A base EditableFormFieldClass that will be extended with {@link EditableFormFieldExtension}
* @mixin EditableFormFieldExtension
*/
class ExtendedEditableFormFieldTestOnly extends EditableFormField implements TestOnly
{
private static $extensions = array(
'EditableFormFieldExtensionTestOnly'
);
}
/**
* Class EditableFormFieldExtension
* Used for testing extensions to EditableFormField and the extended Fields methods
* @property EditableFormField owner
*/
class EditableFormFieldExtensionTestOnly extends DataExtension implements TestOnly
{
private static $db = array(
'TestExtraField' => 'Varchar',
'TestValidationField' => 'Boolean'
);
public function updateFieldConfiguration(FieldList $fields)
{
$extraField = 'TestExtraField';
$fields->push(TextField::create(
$this->owner->getSettingName($extraField),
'Test extra field',
$this->owner->getSetting($extraField)
));
}
public function updateFieldValidationOptions(FieldList $fields)
{
$extraField = 'TestValidationField';
$fields->push(CheckboxField::create(
$this->owner->getSettingName($extraField),
'Test validation field',
$this->owner->getSetting($extraField)
));
}
}

View File

@ -1,8 +1,14 @@
EditableCustomRule:
rule-1:
Display: Hide
ConditionOption: HasValue
FieldValue: 6
EditableOption:
option-1:
Name: Option1
Title: Option 1
option-2:
Name: Option2
Title: Option 2
@ -30,110 +36,104 @@ EditableOption:
option-6:
Name: Option6
Title: Option 6
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
basic-text-2:
Name: basic-text-name
Title: Basic Text Field
required-text:
Name: required-text-field
Title: Required Text Field
CustomErrorMessage: Custom Error Message
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
checkbox-2:
Name: checkbox-1
Title: Checkbox 1
checkbox-with-rule:
Name: checkbox-with-rule
Title: Checkbox with rule
DisplayRules: =>EditableCustomRule.rule-1
EditableCheckboxGroupField:
checkbox-group:
Name: check-box-group
Title: Check box group
Options: =>EditableOption.option-3, =>EditableOption.option-4
EditableEmailField:
email-field:
Name: email-field
Title: Email
EditableRadioField:
radio-field:
Name: radio-option
Title: Radio Option
Options: =>EditableOption.option-5, =>EditableOption.option-6
EditableFileField:
file-field:
Name: file-uploader
Title: Set file
ExtendedEditableFormFieldTestOnly:
extended-field:
Name: extended-field
Title: Extended Field
TestExtraField: Extra Field
TestValidationField: Extra Validation Field
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
custom-rules-form:
Title: Custom Rules Form
Fields: =>EditableCheckbox.checkbox-2, =>EditableTextField.basic-text-2
Fields: =>EditableCheckbox.checkbox-with-rule, =>EditableTextField.basic-text-2
empty-form:
Title: Empty Form

View File

@ -1,46 +0,0 @@
<?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';
protected $editor;
function setUp() {
parent::setUp();
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$controller = new FieldEditorTest_Controller($form);
$fields = $controller->Form()->Fields();
$this->editor = $fields->fieldByName('Fields');
}
function testSaveInto() {
$this->logInWithPermission('ADMIN');
// @todo
}
function testAddField() {
$this->logInWithPermission('ADMIN');
// Debug::show($this->editor->addfield());
}
}
class FieldEditorTest_Controller extends Controller {
public function Form() {
return new Form($this, 'Form', new FieldList(new FieldEditor('Fields')), new FieldList());
}
}

View File

@ -18,7 +18,7 @@ class UserDefinedFormControllerTest extends FunctionalTest {
// load the form
$this->get($form->URLSegment);
$response = $this->submitForm('Form_Form', null, array('basic-text-name' => 'Basic Value'));
$response = $this->submitForm('UserForm_Form', null, array('basic-text-name' => 'Basic Value'));
// should have a submitted form field now
$submitted = DataObject::get('SubmittedFormField', "\"Name\" = 'basic-text-name'");
@ -106,7 +106,7 @@ class UserDefinedFormControllerTest extends FunctionalTest {
$controller = new UserDefinedFormControllerTest_Controller($form);
$fields = $controller->getFormFields();
$fields = $controller->Form()->getFormFields();
$this->assertEquals($fields->Count(), 1);
@ -116,26 +116,26 @@ class UserDefinedFormControllerTest extends FunctionalTest {
UserDefinedForm::config()->required_identifier = "*";
$fields = $controller->getFormFields();
$fields = $controller->Form()->getFormFields();
$this->assertEquals($fields->First()->getCustomValidationMessage()->getValue(), 'Custom Error Message');
$this->assertEquals($fields->First()->Title(), 'Required Text Field <span class=\'required-identifier\'>*</span>');
// test custom right title
$field = $form->Fields()->First();
$field->setSetting('RightTitle', 'Right Title');
$field->RightTitle = 'Right Title';
$field->write();
$controller = new UserDefinedFormControllerTest_Controller($form);
$fields = $controller->getFormFields();
$fields = $controller->Form()->getFormFields();
$this->assertEquals($fields->First()->RightTitle(), "Right Title");
// test empty form
$emptyForm = $this->objFromFixture('UserDefinedForm', 'empty-form');
$controller = new UserDefinedFormControllerTest_Controller($emptyForm);
$this->assertFalse($controller->Form());
$this->assertFalse($controller->Form()->getFormFields()->exists());
}
function testGetFormActions() {
@ -143,7 +143,7 @@ class UserDefinedFormControllerTest extends FunctionalTest {
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$controller = new UserDefinedFormControllerTest_Controller($form);
$actions = $controller->getFormActions();
$actions = $controller->Form()->getFormActions();
// by default will have 1 submit button which links to process
$expected = new FieldList(new FormAction('process', 'Submit'));
@ -153,23 +153,14 @@ class UserDefinedFormControllerTest extends FunctionalTest {
// the custom popup should have a reset button and a custom text
$custom = $this->objFromFixture('UserDefinedForm', 'form-with-reset-and-custom-action');
$controller = new UserDefinedFormControllerTest_Controller($custom);
$actions = $controller->getFormActions();
$actions = $controller->Form()->getFormActions();
$expected = new FieldList(new FormAction('process', 'Custom Button'));
$expected->push(new ResetFormAction("clearForm", "Clear"));
$this->assertEquals($actions, $expected);
}
function testArrayToJson() {
$array = array('1' => 'one', '2' => 'two');
$string = "{\n1:\"one\", 2:\"two\"\n}\n";
$form = new UserDefinedFormControllerTest_Controller();
$this->assertEquals($form->array2json($array), $string);
}
function testRenderingIntoFormTemplate() {
$form = $this->setupFormFrontend();
@ -210,7 +201,7 @@ class UserDefinedFormControllerTest extends FunctionalTest {
}
function checkTemplateIsCorrect($parser) {
$this->assertArrayHasKey(0, $parser->getBySelector('form#Form_Form'));
$this->assertArrayHasKey(0, $parser->getBySelector('form#UserForm_Form'));
// check for the input
$this->assertArrayHasKey(0, $parser->getBySelector('input.text'));

View File

@ -6,7 +6,7 @@
class UserDefinedFormTest extends FunctionalTest {
static $fixture_file = 'userforms/tests/UserDefinedFormTest.yml';
static $fixture_file = 'UserDefinedFormTest.yml';
function testRollbackToVersion() {

View File

@ -121,27 +121,22 @@ UserDefinedForm_EmailRecipientCondition:
blank-rule:
ConditionOption: IsBlank
ConditionField: =>EditableTextField.your-name-field
not-blank-rule:
ConditionOption: IsNotBlank
ConditionField: =>EditableTextField.address-field
equals-rule:
ConditionOption: Equals
ConditionField: =>EditableTextField.street-field
ConditionValue: 'Matches Equals'
not-equals-rule:
ConditionOption: NotEquals
ConditionField: =>EditableTextField.city-field
ConditionValue: 'Matches Not Equals'
# filtered recipient 2
group-equals-rule:
ConditionOption: Equals
ConditionField: =>EditableCheckboxGroupField.colour-checkbox-group
ConditionValue: Red
group-not-equals-rule:
ConditionOption: NotEquals
ConditionField: =>EditableCheckboxGroupField.colour-checkbox-group

View File

@ -0,0 +1,216 @@
<?php
class UserFormsUpgradeServiceTest extends SapphireTest {
static $fixture_file = 'UserFormsUpgradeServiceTest.yml';
public function setUp() {
Config::inst()->update('UserDefinedForm', 'upgrade_on_build', false);
parent::setUp();
// Assign rules programatically
$field1 = $this->objFromFixture('EditableTextField', 'text1');
$field2 = $this->objFromFixture('EditableTextField', 'text2');
$field3 = $this->objFromFixture('EditableTextField', 'text3');
$field3->CustomRules = serialize(array(
array(
'Display' => 'Show',
'ConditionField' => $field1->Name,
'ConditionOption' => 'IsBlank',
'Value' => ''
),
array(
'Display' => 'Hide',
'ConditionField' => $field2->Name,
'ConditionOption' => 'HasValue',
'Value' => 'bob'
)
));
$field3->write();
// Assign settings programatically
$field4 = $this->objFromFixture('EditableTextField', 'text4');
$field4->CustomSettings = serialize(array(
'MinLength' => 20,
'MaxLength' => 100,
'Rows' => 4,
'ExtraClass' => 'special class',
'RightTitle' => 'My Field',
'ShowOnLoad' => '',
'Default' => 'Enter your text here'
));
$field4->write();
$numeric1 = $this->objFromFixture('EditableNumericField', 'numeric1');
$numeric1->CustomSettings = serialize(array(
'RightTitle' => 'Number of %',
'Default' => 1,
'MinValue' => 1,
'MaxValue' => 100,
'ShowOnLoad' => 'Show'
));
$numeric1->write();
$group1 = $this->objFromFixture('Group', 'group1');
$members1 = $this->objFromFixture('EditableMemberListField', 'members1');
$members1->CustomSettings = serialize(array(
'RightTitle' => 'Select group',
'GroupID' => $group1->ID,
'ShowOnLoad' => 'Hide'
));
$members1->write();
$literal1 = $this->objFromFixture('EditableLiteralField', 'literal1');
$literal1->CustomSettings = serialize(array(
'HideFromReports' => 1,
'RightTitle' => 'Literal',
'Content' => '<p>Content</p>',
'ShowOnLoad' => true
));
$literal1->write();
$heading1 = $this->objFromFixture('EditableFormHeading', 'heading1');
$heading1->CustomSettings = serialize(array(
'RightTitle' => 'Right',
'Level' => 3,
'HideFromReports' => true,
'ShowOnLoad' => false
));
$heading1->write();
$folder = $this->objFromFixture('Folder', 'folder1');
$file1 = $this->objFromFixture('EditableFileField', 'file1');
$file1->CustomSettings = serialize(array(
'RightTitle' => 'File field',
'Folder' => $folder->ID
));
$file1->write();
$date1 = $this->objFromFixture('EditableDateField', 'date1');
$date1->CustomSettings = serialize(array(
'RightTitle' => 'Date field',
'DefaultToToday' => '1'
));
$date1->write();
$checkbox1 = $this->objFromFixture('EditableCheckbox', 'checkbox1');
$checkbox1->CustomSettings = serialize(array(
'Default' => true,
'RightTitle' => 'Check this'
));
$checkbox1->write();
}
/**
* @return UserFormsUpgradeService;
*/
protected function getService() {
return singleton('UserFormsUpgradeService');
}
/**
* Tests migration of custom rules
*/
public function testCustomRulesMigration() {
$service = $this->getService();
$service->setQuiet(true);
$service->run();
$field1 = $this->objFromFixture('EditableTextField', 'text1');
$field2 = $this->objFromFixture('EditableTextField', 'text2');
$field3 = $this->objFromFixture('EditableTextField', 'text3');
$this->assertDOSEquals(array(
array(
'Display' => 'Show',
'ConditionFieldID' => $field1->ID,
'ConditionOption' => 'IsBlank'
),
array(
'Display' => 'Hide',
'ConditionFieldID' => $field2->ID,
'ConditionOption' => 'HasValue',
'FieldValue' => 'bob'
)
), $field3->DisplayRules());
}
/**
* Tests migration of all custom settings
*/
public function testCustomSettingsMigration() {
$service = $this->getService();
$service->setQuiet(true);
$service->run();
$group1 = $this->objFromFixture('Group', 'group1');
$form = $this->objFromFixture('UserDefinedForm', 'form-with-settings');
$folder = $this->objFromFixture('Folder', 'folder1');
$this->assertDOSEquals(array(
array(
'ClassName' => 'EditableTextField',
'Title' => 'Text with rule',
'MinLength' => 20,
'MaxLength' => 100,
'Rows' => 4,
'ExtraClass' => 'special class',
'RightTitle' => 'My Field',
'ShowOnLoad' => true,
'Default' => 'Enter your text here',
),
array(
'ClassName' => 'EditableNumericField',
'Title' => 'Numeric 1',
'RightTitle' => 'Number of %',
'Default' => 1,
'MinValue' => 1,
'MaxValue' => 100,
'ShowOnLoad' => true,
),
array(
'ClassName' => 'EditableMemberListField',
'Title' => 'Members 1',
'RightTitle' => 'Select group',
'GroupID' => $group1->ID,
'ShowOnLoad' => false,
),
array(
'ClassName' => 'EditableLiteralField',
'Title' => 'Literal 1',
'HideFromReports' => true,
'RightTitle' => 'Literal',
'Content' => '<p>Content</p>',
'ShowOnLoad' => true,
),
array(
'ClassName' => 'EditableFormHeading',
'Title' => 'Heading 1',
'RightTitle' => 'Right',
'Level' => 3,
'HideFromReports' => true,
'ShowOnLoad' => false,
),
array(
'ClassName' => 'EditableFileField',
'Title' => 'File 1',
'RightTitle' => 'File field',
'FolderID' => $folder->ID,
),
array(
'ClassName' => 'EditableDateField',
'Title' => 'Date 1',
'RightTitle' => 'Date field',
'DefaultToToday' => true,
),
array(
'ClassName' => 'EditableCheckbox',
'Title' => 'Checkbox 1',
'CheckedDefault' => true,
'RightTitle' => 'Check this',
),
), $form->Fields());
}
}

View File

@ -0,0 +1,57 @@
Group:
group1:
Title: 'Awesome Group'
Folder:
folder1:
Title: 'Folder 1'
EditableTextField:
text1:
Name: text1
Title: 'First field'
text2:
Name: text2
Title: 'Second field'
text3:
Name: text3
Title: 'Third field'
text4:
Name: textwithrule
Title: 'Text with rule'
EditableNumericField:
numeric1:
Name: numeric1
Title: 'Numeric 1'
EditableMemberListField:
members1:
Name: members1
Title: 'Members 1'
EditableLiteralField:
literal1:
Name: literal1
Title: 'Literal 1'
EditableFormHeading:
heading1:
Name: heading1
Title: 'Heading 1'
EditableFileField:
file1:
Name: file1
Title: 'File 1'
EditableDateField:
date1:
Name: date1
Title: 'Date 1'
EditableCheckbox:
checkbox1:
Name: checkbox1
Title: 'Checkbox 1'
UserDefinedForm:
form-with-rules:
Title: 'User Defined Form'
Fields: =>EditableTextField.text1,=>EditableTextField.text2,=>EditableTextField.text3
form-with-settings:
Title: 'Form with custom settings'
Fields: =>EditableTextField.text4,=>EditableNumericField.numeric1,=>EditableMemberListField.members1,=>EditableLiteralField.literal1,=>EditableFormHeading.heading1,=>EditableFileField.file1,=>EditableDateField.date1,=>EditableCheckbox.checkbox1