diff --git a/_config.php b/_config.php
new file mode 100644
index 0000000..8c53a52
--- /dev/null
+++ b/_config.php
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/code/UserDefinedForm.php b/code/UserDefinedForm.php
new file mode 100755
index 0000000..cd3dfc8
--- /dev/null
+++ b/code/UserDefinedForm.php
@@ -0,0 +1,341 @@
+ "Varchar",
+ "EmailOnSubmit" => "Boolean",
+ "SubmitButtonText" => "Varchar",
+ "OnCompleteMessage" => "HTMLText"
+ );
+
+ static $defaults = array(
+ "OnCompleteMessage" => "
Thanks, we've received your submission.
",
+ );
+
+ static $has_many = array(
+ "Fields" => "EditableFormField",
+ "Submissions" => "SubmittedForm"
+ );
+
+ protected $fields;
+
+ function getCMSFields($cms) {
+ $fields = parent::getCMSFields($cms);
+
+ $fields->addFieldToTab("Root."._t('UserDefinedForm.FORM', 'Form'), new FieldEditor("Fields", 'Fields', "", $this ));
+ $fields->addFieldToTab("Root."._t('UserDefinedForm.SUBMISSIONS','Submissions'), new SubmittedFormReportField( "Reports", _t('UserDefinedForm.RECEIVED', 'Received Submissions'), "", $this ) );
+ $fields->addFieldToTab("Root.Content."._t('UserDefinedForm.ONCOMPLETE','On complete'), new HtmlEditorField( "OnCompleteMessage", _t('UserDefinedForm.ONCOMPLETELABEL', 'Show on completion'),3,"",_t('UserDefinedForm.ONCOMPLETEMESSAGE', $this->OnCompleteMessage), $this ) );
+
+ return $fields;
+ }
+
+ function FilterForm() {
+ // Build fields
+ $fields = new FieldSet();
+ $required = array();
+
+ foreach( $this->Fields() as $field ) {
+ $fields->push( $field->getFilterField() );
+ }
+
+ // Build actions
+ $actions = new FieldSet(
+ new FormAction( "filter", _t('UserDefinedForm.SUBMIT', 'Submit') )
+ );
+
+ // set the name of the form
+ return new Form( $this, "Form", $fields, $actions );
+ }
+
+ /**
+ * Filter the submissions by the given criteria
+ */
+ function filter( $data, $form ) {
+
+ $filterClause = array( "`SubmittedForm`.`ParentID` = '{$this->ID}'" );
+
+ $keywords = preg_split( '/\s+/', $data['FilterKeyword'] );
+
+ $keywordClauses = array();
+
+ // combine all keywords into one clause
+ foreach( $keywords as $keyword ) {
+
+ // escape %, \ and _ in the keyword. These have special meanings in a LIKE string
+ $keyword = preg_replace( '/([%_])/', '\\\\1', addslashes( $keyword ) );
+
+ $keywordClauses[] = "`Value` LIKE '%$keyword%'";
+ }
+
+ if( count( $keywordClauses ) > 0 ) {
+ $filterClause[] = "( " . implode( ' OR ', $keywordClauses ) . ")";
+ $searchQuery = 'keywords \'' . implode( "', '", $keywords ) . '\' ';
+ }
+
+ $fromDate = addslashes( $data['FilterFromDate'] );
+ $toDate = addslashes( $data['FilterToDate'] );
+
+ // use date objects to convert date to value expected by database
+ if( ereg('^([0-9]+)/([0-9]+)/([0-9]+)$', $fromDate, $parts) )
+ $fromDate = $parts[3] . '-' . $parts[2] . '-' . $parts[1];
+
+ if( ereg('^([0-9]+)/([0-9]+)/([0-9]+)$', $toDate, $parts) )
+ $toDate = $parts[3] . '-' . $parts[2] . '-' . $parts[1];
+
+ if( $fromDate ) {
+ $filterClause[] = "`SubmittedForm`.`Created` >= '$fromDate'";
+ $searchQuery .= 'from ' . $fromDate . ' ';
+ }
+
+ if( $toDate ) {
+ $filterClause[] = "`SubmittedForm`.`Created` <= '$toDate'";
+ $searchQuery .= 'to ' . $toDate;
+ }
+
+ $submittedValues = DataObject::get( 'SubmittedFormField', implode( ' AND ', $filterClause ), "", "INNER JOIN `SubmittedForm` ON `SubmittedFormField`.`ParentID`=`SubmittedForm`.`ID`" );
+
+ if( !$submittedValues || $submittedValues->Count() == 0 )
+ return _t('UserDefinedForm.NORESULTS', 'No matching results found');
+
+ $submissions = $submittedValues->groupWithParents( 'ParentID', 'SubmittedForm' );
+
+ if( !$submissions || $submissions->Count() == 0 )
+ return _t('UserDefinedForm.NORESULTS', 'No matching results found');
+
+ return $submissions->customise(
+ array( 'Submissions' => $submissions )
+ )->renderWith( 'SubmittedFormReportField_Reports' );
+ }
+
+ function ReportFilterForm() {
+ return new SubmittedFormReportField_FilterForm( $this, 'ReportFilterForm' );
+ }
+
+ function delete() {
+ // remove all the fields associated with this page
+ foreach( $this->Fields() as $field )
+ $field->delete();
+
+ parent::delete();
+ }
+
+ public function customFormActions( $isReadonly = false ) {
+ return new FieldSet( new TextField( "SubmitButtonText", _t('UserDefinedForm.TEXTONSUBMIT', 'Text on submit button:'), $this->SubmitButtonText ) );
+ }
+
+ /**
+ * Duplicate this UserDefinedForm page, and its form fields.
+ * Submissions, on the other hand, won't be duplicated.
+ */
+ public function duplicate() {
+ $page = parent::duplicate();
+ foreach($this->Fields() as $field) {
+ $newField = $field->duplicate();
+ $newField->ParentID = $page->ID;
+ $newField->write();
+ }
+ return $page;
+ }
+}
+
+/**
+ * Controller for the {@link UserDefinedForm} page type.
+ * @package cms
+ * @subpackage pagetypes
+ */
+class UserDefinedForm_Controller extends Page_Controller {
+
+ function init() {
+ Requirements::javascript(THIRDPARTY_DIR . 'jsparty/prototype-safe.js');
+ Requirements::javascript(THIRDPARTY_DIR . 'jsparty/behaviour.js');
+
+ parent::init();
+ }
+
+ function Form() {
+ // Build fields
+ $fields = new FieldSet();
+ $required = array();
+
+ if( !$this->SubmitButtonText )
+ $this->SubmitButtonText = 'Submit';
+
+ foreach( $this->Fields() as $field ) {
+ $fields->push( $field->getFormField() );
+ if( $field->Required )
+ $required[] = $field->Name;
+ }
+
+ if(!isset($_SERVER['HTTP_REFERER'])) {
+ $_SERVER['HTTP_REFERER'] = "";
+ }
+
+ $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
+ $fields->push( new HiddenField( "Referrer", "", $referer ) );
+
+ // Build actions
+ $actions = new FieldSet(
+ new FormAction( "process", $this->SubmitButtonText )
+ );
+
+ // set the name of the form
+ $form = new Form( $this, "Form", $fields, $actions, new RequiredFields( $required ) );
+ $form->loadDataFrom($this->failover);
+ return $form;
+ }
+
+ function ReportFilterForm() {
+ return new SubmittedFormReportField_FilterForm( $this, 'ReportFilterForm' );
+ }
+
+ function process( $data, $form ) {
+ $submittedForm = new SubmittedForm();
+ $submittedForm->SubmittedBy = Member::currentUser();
+ $submittedForm->ParentID = $this->ID;
+ $submittedForm->Recipient = $this->EmailTo;
+ $submittedForm->write();
+
+ $values = array();
+ $recipientAddresses = array();
+ $sendCopy = false;
+
+ $attachments = array();
+
+ $submittedFields = new DataObjectSet();
+ foreach( $this->Fields() as $field ) {
+ $submittedField = new SubmittedFormField();
+ $submittedField->ParentID = $submittedForm->ID;
+ $submittedField->Name = $field->Name;
+ $submittedField->Title = $field->Title;
+
+ if( $field->hasMethod( 'getValueFromData' ) )
+ $submittedField->Value = $field->getValueFromData( $data );
+ else
+ if(isset($data[$field->Name])) $submittedField->Value = $data[$field->Name];
+
+ $submittedField->write();
+ $submittedFields->push($submittedField);
+
+ if(!empty( $data[$field->Name])){
+ // execute the appropriate functionality based on the form field.
+ switch($field->ClassName){
+
+ case "EditableEmailField" :
+
+ if($field->SendCopy){
+ $recipientAddresses[] = $data[$field->Name];
+ $sendCopy = true;
+ $values[$field->Title] = ''.$data[$field->Name].' ';
+ }
+
+ break;
+
+ case "EditableFileField" :
+
+ // Returns a file type which we attach to the email.
+ $submittedfile = $field->createSubmittedField($data[$field->Name], $submittedForm);
+ $file = $submittedfile->UploadedFile();
+
+ $filename = $file->getFilename();
+
+ // Attach the file if its less than 1MB, provide a link if its over.
+ if($file->getAbsoluteSize() < 1024*1024*1){
+ $attachments[] = $file;
+ }
+
+ // Always provide the link if present.
+ if($file->ID) {
+ $submittedField->Value = $values[$field->Title] = "Uploaded to: ". Director::absoluteBaseURL(). $filename . " ";
+ } else {
+ $submittedField->Value = $values[$field->Title] = "";
+ }
+
+ break;
+ }
+
+ }elseif( $field->hasMethod( 'getValueFromData' ) ) {
+ $values[$field->Title] = Convert::linkIfMatch($field->getValueFromData( $data ));
+
+ } else {
+ if(isset($data[$field->Name])) $values[$field->Title] = Convert::linkIfMatch($data[$field->Name]);
+ }
+
+ }
+
+ if( $this->EmailOnSubmit || $sendCopy ) {
+ $emailData = array(
+ "Recipient" => $this->EmailTo,
+ "Sender" => Member::currentUser(),
+ "Fields" => $submittedFields,
+ );
+
+ $email = new UserDefinedForm_SubmittedFormEmail($submittedFields);
+ $email->populateTemplate($emailData);
+ $email->setTo( $this->EmailTo );
+ $email->setSubject( $this->Title );
+
+ // add attachments to email (<1MB)
+ if($attachments){
+ foreach($attachments as $file){
+ $email->attachFile($filename,$filename);
+ }
+ }
+
+ $email->send();
+
+ // send to each of email fields
+ foreach( $recipientAddresses as $addr ) {
+ $email->setTo( $addr );
+ $email->send();
+ }
+ }
+
+ $custom = $this->customise(array(
+ "Content" => $this->customise( array( 'Link' => $data['Referrer'] ) )->renderWith('ReceivedFormSubmission'),
+ "Form" => " ",
+ ));
+
+ return $custom->renderWith('Page');
+ }
+}
+
+/**
+ * Email that gets sent when a submission is made.
+ * @package cms
+ * @subpackage pagetypes
+ */
+class UserDefinedForm_SubmittedFormEmail extends Email {
+ protected $ss_template = "SubmittedFormEmail";
+ protected $from = '$Sender.Email';
+ protected $to = '$Recipient.Email';
+ protected $subject = 'Submission of form';
+ protected $data;
+
+ function __construct($values) {
+ $this->subject = _t('UserDefinedForm_SubmittedFormEmail.EMAILSUBJECT', 'Submission of form');
+ parent::__construct();
+
+ $this->data = $values;
+ }
+
+ function Data() {
+ return $this->data;
+ }
+}
+
+?>
diff --git a/code/editor/EditableButton.php b/code/editor/EditableButton.php
new file mode 100755
index 0000000..b03d97d
--- /dev/null
+++ b/code/editor/EditableButton.php
@@ -0,0 +1,14 @@
+ ';
+ }
+}
+?>
\ No newline at end of file
diff --git a/code/editor/EditableCheckbox.php b/code/editor/EditableCheckbox.php
new file mode 100755
index 0000000..27df716
--- /dev/null
+++ b/code/editor/EditableCheckbox.php
@@ -0,0 +1,45 @@
+ "Boolean"
+ );
+
+ static $singular_name = 'Checkbox';
+ static $plural_name = 'Checkboxes';
+
+ function CheckboxField() {
+ $checkbox = new CheckboxField("Fields[".$this->ID."][Default]", "Checked by default", $this->getField('Default'));
+
+ if( $this->readonly )
+ $checkbox = $checkbox->performReadonlyTransformation();
+
+ return $checkbox->FieldHolder();
+ }
+
+ function populateFromPostData( $data ) {
+ $this->setField('Checked', isset($data['Checked']) ? $data['Checked'] : null);
+ parent::populateFromPostData( $data );
+ }
+
+ function getFormField() {
+ return new CheckboxField( $this->Name, $this->Title, $this->getField('Default') );
+ }
+
+ function getFilterField() {
+ return new OptionsetField( $this->Name,
+ $this->Title,
+ array( '-1' => '('._t('EditableCheckbox.ANY', 'Any').')',
+ 'on' => _t('EditableCheckbox.SELECTED', 'Selected'),
+ '0' => _t('EditableCheckbox.NOTSELECTED', 'Not selected') )
+ );
+ }
+}
+?>
\ No newline at end of file
diff --git a/code/editor/EditableCheckboxGroupField.php b/code/editor/EditableCheckboxGroupField.php
new file mode 100755
index 0000000..21bcb51
--- /dev/null
+++ b/code/editor/EditableCheckboxGroupField.php
@@ -0,0 +1,156 @@
+readonly = true;
+ return $this->Option();
+ }
+
+ function isReadonly() {
+ return $this->readonly;
+ }
+
+ static $has_many = array(
+ "Options" => "EditableCheckboxOption"
+ );
+
+ static $singular_name = "Checkbox group";
+ static $plural_name = "Checkbox groups";
+
+ function duplicate() {
+ $clonedNode = parent::duplicate();
+
+ foreach( $this->Options() as $field ) {
+ $newField = $field->duplicate();
+ $newField->ParentID = $clonedNode->ID;
+ $newField->write();
+ }
+
+ return $clonedNode;
+ }
+
+ function delete() {
+ $options = $this->Options();
+
+ foreach( $options as $option )
+ $option->delete();
+
+ parent::delete();
+ }
+
+ function EditSegment() {
+ return $this->renderWith( $this->class );
+ }
+
+ function populateFromPostData( $data ) {
+ parent::populateFromPostData( $data );
+
+ $fieldSet = $this->Options();
+
+ $deletedOptions = explode( ',', $data['Deleted'] );
+
+
+ // store default, etc
+ foreach( $fieldSet as $option ) {
+ if( $deletedOptions && array_search( $option->ID, $deletedOptions ) !== false ) {
+ $option->delete();
+ continue;
+ }
+
+ if( $data[$option->ID] )
+ $option->populateFromPostData( $data[$option->ID] );
+
+ unset( $data[$option->ID] );
+ }
+
+ foreach( $data as $tempID => $optionData ) {
+
+ if( !$tempID || !is_array( $optionData ) || empty( $optionData ) || !preg_match('/^_?\d+$/', $tempID ) )
+ continue;
+
+ // what will we name the new option?
+ $newOption = new EditableCheckboxOption();
+ $newOption->Name = 'option' . (string)$optionNumber++;
+ $newOption->ParentID = $this->ID;
+ $newOption->populateFromPostData( $optionData );
+ }
+ }
+
+ function DefaultOption() {
+ $defaultOption = 0;
+
+ foreach( $this->Options() as $option ) {
+ if( $option->getField('Default') )
+ return $defaultOption;
+ else
+ $defaultOption++;
+ }
+
+ return -1;
+ }
+
+ function getFormField() {
+ return $this->createField();
+ }
+
+ function getFilterField() {
+ return $this->createField( true );
+ }
+
+ function createField( $asFilter = false ) {
+ $optionSet = $this->Options();
+ $options = array();
+
+ if( $asFilter )
+ $options['-1'] = '(Any)';
+
+ $defaultOption = '-1';
+
+ /*foreach( $optionSet as $option ) {
+ $options[$option->Title] = $option->Title;
+ }*/
+
+ // return radiofields
+ $checkboxSet = new CheckboxSetField( $this->Name, $this->Title, $optionSet, $optionSet );
+
+ return $checkboxSet;
+ }
+
+ function getValueFromData($data) {
+ if(empty($data[$this->Name])) {
+ return "";
+ }
+
+ $entries = $data[$this->Name];
+
+ if(!is_array($data[$this->Name])) {
+ $entries = array($data[$this->Name]);
+ }
+
+ $selectedOptions = DataObject::get('EditableCheckboxOption', "ParentID={$this->ID} AND ID IN (" . implode(',', $entries) . ")");
+ foreach($selectedOptions as $selected) {
+ if(!$result) {
+ $result = $selected->Title;
+ } else {
+ $result .= "," . $selected->Title;
+ }
+ }
+
+ return $result;
+ }
+
+ function TemplateOption() {
+ $option = new EditableCheckboxOption();
+ return $option->EditSegment();
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/code/editor/EditableCheckboxOption.php b/code/editor/EditableCheckboxOption.php
new file mode 100755
index 0000000..0b5eda7
--- /dev/null
+++ b/code/editor/EditableCheckboxOption.php
@@ -0,0 +1,76 @@
+ "Varchar",
+ "Title" => "Varchar",
+ "Default" => "Boolean",
+ "Sort" => "Int"
+ );
+ static $has_one = array(
+ "Parent" => "EditableCheckboxGroupField",
+ );
+
+ static $singular_name = "Checkbox option";
+ static $plural_name = "Checkbox options";
+
+ function EditSegment() {
+ return $this->renderWith('EditableFormFieldOption');
+ }
+
+ function TitleField() {
+ return new TextField( "Fields[{$this->ParentID}][{$this->ID}][Title]", null, $this->Title );
+ }
+
+ function Name() {
+ return "Fields[{$this->ParentID}][{$this->ID}]";
+ }
+
+ function populateFromPostData( $data ) {
+ $this->Title = $data['Title'];
+ $this->setField('Default', $data['Default']);
+ $this->Sort = $data['Sort'];
+ $this->write();
+ }
+
+ function Option() {
+ // return new radio field
+ /*$title = Convert::raw2att( $this->Title );
+
+ $default = "";
+
+ if( $this->getField('Default') )
+ $default = '+';
+ else
+ $default = '-';
+
+ //Debug::show($this);
+ return ' ';*/
+
+ return $this->EditSegment();
+ }
+
+ function ReadonlyOption() {
+ $this->readonly = true;
+ return $this->EditSegment();
+ }
+
+ function DefaultSelect() {
+ if( $this->readonly )
+ $disabled = " disabled=\"disabled\"";
+
+ if( $this->getField('Default') )
+ $default = " checked=\"checked\"";
+
+ return " ParentID}][{$this->ID}][Default]\" value=\"1\"".$disabled.$default." />";
+ }
+}
+?>
\ No newline at end of file
diff --git a/code/editor/EditableDateField.php b/code/editor/EditableDateField.php
new file mode 100755
index 0000000..ae21e3a
--- /dev/null
+++ b/code/editor/EditableDateField.php
@@ -0,0 +1,49 @@
+ID}][Default]", "", $this->getField('Default') );
+
+ if( $this->readonly )
+ $dmyField = $dmyField->performReadonlyTransformation();
+
+ return $dmyField;
+ }
+
+ function populateFromPostData( $data ) {
+ /*if( !empty( $data['Default'] ) && !preg_match( '/^\d{4}-\d{2}-\d{2}$/', $data['Default'] ) ) {
+ if( empty( $data['Year'] ) || !is_numeric( $data['Year'] ) ) $data['Year'] = '2001';
+ if( empty( $data['Month'] ) || !is_numeric( $data['Month'] ) ) $data['Month'] = '01';
+ if( empty( $data['Day'] ) || !is_numeric( $data['Day'] ) ) $data['Day'] = '01';
+
+ // unset( $data['Default'] );
+ $data['Default'] = $data['Year'] . '-' . $data['Month'] . '-' . $data['Day'];
+ }*/
+
+ /*echo "ERROR:";
+ Debug::show( $data );
+ die();*/
+
+ $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'];
+
+ // Debug::show( $data );
+
+ parent::populateFromPostData( $data );
+ }
+
+ function getFormField() {
+ return new CalendarDateField( $this->Name, $this->Title, $this->getField('Default') );
+ }
+}
+?>
\ No newline at end of file
diff --git a/code/editor/EditableDropdown.php b/code/editor/EditableDropdown.php
new file mode 100755
index 0000000..c0d3f67
--- /dev/null
+++ b/code/editor/EditableDropdown.php
@@ -0,0 +1,127 @@
+ "EditableDropdownOption"
+ );
+
+ static $singular_name = 'Dropdown';
+ static $plural_name = 'Dropdowns';
+
+ function delete() {
+ $options = $this->Options();
+
+ foreach( $options as $option )
+ $option->delete();
+
+ parent::delete();
+ }
+
+ function EditSegment() {
+ return $this->renderWith( $this->class );
+ }
+
+ function populateFromPostData( $data ) {
+
+ parent::populateFromPostData( $data );
+
+ $fieldSet = $this->Options();
+
+ $deletedOptions = explode( ',', $data['Deleted'] );
+
+ // store default, etc
+ foreach( $fieldSet as $option ) {
+
+ if( $deletedOptions && array_search( $option->ID, $deletedOptions ) !== false ) {
+ $option->delete();
+ continue;
+ }
+
+ if(isset($data[$option->ID])) {
+ $option->setField('Default', isset($data['Default']) ? ($option->ID == $data['Default']) : false);
+ $option->populateFromPostData( $data[$option->ID] );
+ }
+
+ unset( $data[$option->ID] );
+ }
+
+ $optionNumber = 0;
+ foreach( $data as $tempID => $optionData ) {
+
+ if( !$tempID || !is_array( $optionData ) || empty( $optionData ) || !preg_match('/^_?\d+$/', $tempID ) )
+ continue;
+
+ // what will we name the new option?
+ $newOption = new EditableDropdownOption();
+ $newOption->Name = 'option' . (string)$optionNumber++;
+ $newOption->ParentID = $this->ID;
+ if(isset($data['Default'])) {
+ $newOption->setField('Default', $tempID == $data['Default']);
+ }
+
+ if( Director::is_ajax() ) {
+ $fieldID = $this->ID;
+ $fieldEditorName = $this->editor ? $this->editor->Name() : 'Fields';
+ $prefix = $fieldEditorName . '[' . $fieldID . ']';
+ $newID = $newOption->ID;
+ $newSort = $newOption->Sort;
+ echo "\$('". $fieldEditorName . "[$fieldID]').updateOption('$prefix','$tempID','$newID','$newSort');";
+ }
+
+ if( !$optionData['Sort'] ) {
+
+ }
+
+ $newOption->populateFromPostData( $optionData );
+ }
+ }
+
+ function getFormField() {
+ return $this->createField();
+ }
+
+ function getFilterField() {
+ return $this->createField( true );
+ }
+
+ function createField( $asFilter = false ) {
+ $optionSet = $this->Options();
+ $options = array();
+
+ if( $asFilter )
+ $options['-1'] = "(Any)";
+
+ $defaultOption = '-1';
+
+ foreach( $optionSet as $option ) {
+ $options[$option->Title] = $option->Title;
+ if( $option->getField('Default') && !$asFilter ) $defaultOption = $option->Title;
+ }
+
+ return new DropdownField( $this->Name, $this->Title, $options, $defaultOption );
+ }
+
+ function TemplateOption() {
+ $option = new EditableDropdownOption();
+ return $option->EditSegment();
+ }
+
+ function duplicate() {
+ $clonedNode = parent::duplicate();
+
+ foreach( $this->Options() as $field ) {
+ $newField = $field->duplicate();
+ $newField->ParentID = $clonedNode->ID;
+ $newField->write();
+ }
+
+ return $clonedNode;
+ }
+}
+?>
diff --git a/code/editor/EditableDropdownOption.php b/code/editor/EditableDropdownOption.php
new file mode 100755
index 0000000..7d1bfd0
--- /dev/null
+++ b/code/editor/EditableDropdownOption.php
@@ -0,0 +1,77 @@
+readonly = true;
+ return $this->EditSegment();
+ }
+
+ function isReadonly() {
+ return $this->readonly;
+ }
+
+ static $default_sort = "Sort";
+
+ // add required here?
+ static $db = array(
+ "Name" => "Varchar",
+ "Title" => "Varchar",
+ "Default" => "Boolean",
+ "Sort" => "Int"
+ );
+ static $has_one = array(
+ "Parent" => "EditableDropdown",
+ );
+
+ static $singular_name = 'Dropdown option';
+ static $plural_name = 'Dropdown options';
+
+ function EditSegment() {
+ return $this->renderWith('EditableFormFieldOption');
+ }
+
+ function TitleField() {
+ return new TextField( "Fields[{$this->ParentID}][{$this->ID}][Title]", null, $this->Title );
+ }
+
+ function Name() {
+ return "Fields[{$this->ParentID}][{$this->ID}]";
+ }
+
+ function populateFromPostData( $data ) {
+ $this->Title = $data['Title'];
+ $this->Sort = $data['Sort'];
+ $this->write();
+ }
+
+ function Option() {
+ // return new radio field
+ /*$title = $this->Title;
+
+ $default = "";
+
+ if( $this->getField('Default') )
+ $default = 'class="default"';
+
+ //Debug::show($this);
+ return ' ';*/
+
+ return $this->EditSegment();
+ }
+
+ function DefaultSelect() {
+ $disabled = ($this->readonly) ? " disabled=\"disabled\"" : '';
+
+ $default = ($this->Parent()->getField('Default') == $this->ID) ? " checked=\"checked\"" : "";
+
+ return " ParentID}][Default]\" value=\"{$this->ID}\"".$disabled.$default." />";
+ }
+}
+?>
diff --git a/code/editor/EditableEmailField.php b/code/editor/EditableEmailField.php
new file mode 100755
index 0000000..737c622
--- /dev/null
+++ b/code/editor/EditableEmailField.php
@@ -0,0 +1,53 @@
+ "Boolean"
+ );
+
+ static $singular_name = 'Email field';
+ static $plural_name = 'Email fields';
+
+ function populateFromPostData( $data ) {
+ $this->SendCopy = !empty( $data['SendCopy'] ) ? "1" : "0";
+
+ parent::populateFromPostData( $data );
+ }
+
+ function ExtraOptions() {
+ $baseName = "Fields[$this->ID]";
+
+ $extraFields = new FieldSet(
+ new CheckboxField( $baseName . "[SendCopy]", _t('EditableEmailField.SENDCOPY', 'Send copy of submission to this address'), $this->SendCopy )
+ );
+
+ foreach( parent::ExtraOptions() as $extraField )
+ $extraFields->push( $extraField );
+
+ if( $this->readonly )
+ $extraFields = $extraFields->makeReadonly();
+
+ return $extraFields;
+ }
+
+ function getFormField() {
+ return new EmailField( $this->Name, $this->Title, $this->getField('Default') );
+ }
+
+ function getFilterField() {
+ return $this->createField( true );
+ }
+
+ function DefaultField() {
+ $disabled = ($this->readonly) ? " disabled=\"disabled\"" : '';
+
+ return ' ';
+ }
+}
+?>
\ No newline at end of file
diff --git a/code/editor/EditableFileField.php b/code/editor/EditableFileField.php
new file mode 100755
index 0000000..7283387
--- /dev/null
+++ b/code/editor/EditableFileField.php
@@ -0,0 +1,65 @@
+ "File"
+ );
+
+ /**
+ * @see {Upload->allowedMaxFileSize}
+ * @var int
+ */
+ public static $allowed_max_file_size;
+
+ /**
+ * @see {Upload->allowedExtensions}
+ * @var array
+ */
+ public static $allowed_extensions = array();
+
+ static $singular_name = 'File field';
+ static $plural_names = 'File fields';
+
+ function getFormField() {
+ if($field = parent::getFormField())
+ return $field;
+ return new FileField($this->Name, $this->Title, $this->getField('Default'));
+ // TODO We can't use the preview feature because FileIFrameField also shows the "From the file store" functionality
+ //return new FileIFrameField( $this->Name, $this->Title, $this->getField('Default') );
+ }
+
+ function getSimpleFormField(){
+ return new FileField($this->Name, $this->Title, $this->getField('Default'));
+ }
+
+ function createSubmittedField($data, $submittedForm, $fieldClass = "SubmittedFileField") {
+ if(!$_FILES[$this->Name])
+ return null;
+
+ $submittedField = new $fieldClass();
+ $submittedField->Title = $this->Title;
+ $submittedField->Name = $this->Name;
+ $submittedField->ParentID = $submittedForm->ID;
+
+ // create the file from post data
+ $upload = new Upload();
+ $upload->setAllowedExtensions(self::$allowed_extensions);
+ $upload->setAllowedMaxFileSize(self::$allowed_max_file_size);
+
+ // upload file
+ $upload->load($_FILES[$this->Name]);
+
+ $uploadedFile = $upload->getFile();
+ $submittedField->UploadedFileID = $uploadedFile->ID;
+ $submittedField->write();
+
+ return $submittedField;
+ }
+}
+?>
\ No newline at end of file
diff --git a/code/editor/EditableFormField.php b/code/editor/EditableFormField.php
new file mode 100755
index 0000000..b23b682
--- /dev/null
+++ b/code/editor/EditableFormField.php
@@ -0,0 +1,213 @@
+ "Varchar",
+ "Title" => "Varchar(255)",
+ "Default" => "Varchar",
+ "Sort" => "Int",
+ "Required" => "Boolean",
+ "CanDelete" => "Boolean",
+ "CustomParameter" => "Varchar"
+ );
+
+ static $defaults = array(
+ "CanDelete" => "1"
+ );
+
+ static $has_one = array(
+ "Parent" => "SiteTree",
+ );
+
+ protected $readonly;
+
+ protected $editor;
+
+ function setEditor( $editor ) {
+ $this->editor = $editor;
+ }
+
+ function __construct( $record = null, $isSingleton = false ) {
+ $this->setField('Default', -1);
+ parent::__construct( $record, $isSingleton );
+ }
+
+ function EditSegment() {
+ return $this->renderWith('EditableFormField');
+ }
+
+ function isReadonly() {
+ return $this->readonly;
+ }
+
+ function ClassName() {
+ return $this->class;
+ }
+
+ function makeReadonly() {
+ $this->readonly = true;
+ return $this;
+ }
+
+ function ReadonlyEditSegment() {
+ $this->readonly = true;
+ return $this->EditSegment();
+ }
+
+ function TitleField() {
+ // return new TextField( "Fields[".$this->ID."][Title]", null, $this->Title );
+ $titleAttr = Convert::raw2att($this->Title);
+ $readOnlyAttr = '';
+
+ if( $this->readonly ) {
+ $readOnlyAttr = ' disabled="disabled"';
+ } else {
+ $readOnlyAttr = '';
+ }
+
+ return " ID}][Title]\"$readOnlyAttr />";
+ }
+
+ function Name() {
+ return "Fields[".$this->ID."]";
+ }
+
+ /*function getName() {
+ return "field" . $this->ID;
+ }*/
+
+ function populateFromPostData( $data ) {
+
+ $this->Title = $data['Title'];
+ if(isset($data['Default'])) {
+ $this->setField('Default', $data['Default']);
+ }
+ $this->Sort = isset($data['Sort']) ? $data['Sort'] : null;
+ $this->CustomParameter = $data['CustomParameter'];
+ $this->Required = !empty( $data['Required'] ) ? 1 : 0;
+ $this->CanDelete = ( isset( $data['CanDelete'] ) && !$data['CanDelete'] ) ? 0 : 1;
+ $this->write();
+
+ // The field must be written to ensure a unique ID.
+ $this->Name = $this->class.$this->ID;
+ $this->write();
+ }
+
+ function ExtraOptions() {
+
+ $baseName = "Fields[$this->ID]";
+ $extraOptions = new FieldSet();
+
+ if( !$this->Parent()->hasMethod( 'hideExtraOption' ) ){
+ $extraOptions->push( new CheckboxField($baseName . "[Required]", _t('EditableFormField.REQUIRED', 'Required?'), $this->Required) );
+ }elseif( !$this->Parent()->hideExtraOption( 'Required' ) ){
+ $extraOptions->push( new CheckboxField($baseName . "[Required]", _t('EditableFormField.REQUIRED', 'Required?'), $this->Required) );
+ }
+
+ if( $this->Parent()->hasMethod( 'getExtraOptionsForField' ) ) {
+ $extraFields = $this->Parent()->getExtraOptionsForField( $this );
+
+ foreach( $extraFields as $extraField )
+ $extraOptions->push( $extraField );
+ }
+
+ if( $this->readonly )
+ $extraOptions = $extraOptions->makeReadonly();
+
+ return $extraOptions;
+ }
+
+ /**
+ * Return a FormField to appear on the front end
+ */
+ function getFormField() {
+ }
+
+ function getFilterField() {
+
+ }
+
+ /**
+ * Return an evaluation appropriate for a filter clause
+ * @todo: escape the string
+ */
+ function filterClause( $value ) {
+ // Not filtering on this field
+
+ if( $value == '-1' )
+ return "";
+ else
+ return "`{$this->name}` = '$value'";
+ }
+
+ function showInReports() {
+ return true;
+ }
+
+ function prepopulate( $value ) {
+ $this->prepopulateFromMap( $this->parsePrepopulateValue( $value ) );
+ }
+
+ protected function parsePrepopulateValue( $value ) {
+ $paramList = explode( ',', $value );
+
+ $paramMap = array();
+
+ foreach( $paramList as $param ) {
+
+ if( preg_match( '/([^=]+)=(.+)/', $param, $match ) ) {
+ if( isset( $paramMap[$match[1]] ) && is_array( $paramMap[$match[1]] ) ) {
+ $paramMap[$match[1]][] = $match[2];
+ } else if( isset( $paramMap[$match[1]] ) ) {
+ $paramMap[$match[1]] = array( $paramMap[$match[1]] );
+ $paramMap[$match[1]][] = $match[2];
+ //Debug::message( $match[1] . '[]=' . $match[2] );
+ } else {
+ $paramMap[$match[1]] = $match[2];
+ //Debug::message( $match[1] . '=' . $match[2] );
+ }
+ } else {
+ //Debug::message('Invalid: ' . $param );
+ }
+ }
+
+ //Debug::show( $paramMap );
+
+ return $paramMap;
+ }
+
+ protected function prepopulateFromMap( $paramMap ) {
+ //Debug::show( $paramMap );
+ //Debug::show( $this->stat('db') );
+
+ foreach( $paramMap as $field => $fieldValue ) {
+ if( /*$this->hasField( $field ) &&*/ !is_array( $fieldValue ) ) {
+ $this->$field = $fieldValue;
+ // Debug::message( 'Set ' . $field . ':'. $fieldValue );
+ }
+ }
+
+ // exit();
+ }
+
+ function Type() {
+ return $this->class;
+ }
+
+ function CustomParameter() {
+ return $this->CustomParameter;
+ }
+ /*
+ function saveInto( DataObject $record ) {
+ if(
+ }
+ */
+}
+?>
\ No newline at end of file
diff --git a/code/editor/EditableFormHeading.php b/code/editor/EditableFormHeading.php
new file mode 100755
index 0000000..053f8dd
--- /dev/null
+++ b/code/editor/EditableFormHeading.php
@@ -0,0 +1,21 @@
+Title, 'FormHeading' );
+ // return '' . $this->Title . ' ';
+ }
+
+ function showInReports() {
+ return false;
+ }
+}
+?>
\ No newline at end of file
diff --git a/code/editor/EditableMemberListField.php b/code/editor/EditableMemberListField.php
new file mode 100644
index 0000000..eeb6f95
--- /dev/null
+++ b/code/editor/EditableMemberListField.php
@@ -0,0 +1,44 @@
+ 'Group'
+ );
+
+ static $singular_name = 'Member list field';
+ static $plural_name = 'Member list fields';
+
+ public function DefaultField() {
+ // return new TreeDropdownField( "Fields[{$this->ID}][GroupID]", 'Group' );
+
+ $groups = DataObject::get('Group');
+
+ foreach( $groups as $group )
+ $groupArray[$group->ID] = $group->Title;
+
+ return new DropdownField( "Fields[{$this->ID}][GroupID]", 'Group', $groupArray, $this->GroupID );
+ }
+
+ public function populateFromPostData( $data ) {
+ $this->GroupID = $data['GroupID'];
+
+ parent::populateFromPostData( $data );
+ }
+
+ function getFormField() {
+ return new DropdownField( $this->Name, $this->Title, Member::mapInGroups( $this->GroupID ) );
+ }
+
+ function getValueFromData( $data ) {
+ $value = $data[$this->Name];
+
+ $member = DataObject::get_one('Member', "Member.ID = {$value}");
+ return $member->getName();
+ }
+}
+?>
\ No newline at end of file
diff --git a/code/editor/EditableRadioField.php b/code/editor/EditableRadioField.php
new file mode 100755
index 0000000..820464c
--- /dev/null
+++ b/code/editor/EditableRadioField.php
@@ -0,0 +1,187 @@
+ "EditableRadioOption"
+ );
+
+ static $singular_name = 'Radio field';
+ static $plural_name = 'Radio fields';
+
+ function delete() {
+ $options = $this->Options();
+
+ foreach( $options as $option )
+ $option->delete();
+
+ parent::delete();
+ }
+
+ function duplicate() {
+ $clonedNode = parent::duplicate();
+
+ foreach( $this->Options() as $field ) {
+ $newField = $field->duplicate();
+ $newField->ParentID = $clonedNode->ID;
+ $newField->write();
+ }
+
+ return $clonedNode;
+ }
+
+ function EditSegment() {
+ return $this->renderWith( $this->class );
+ }
+
+ function populateFromPostData( $data ) {
+ parent::populateFromPostData( $data );
+
+ $fieldSet = $this->Options();
+ $deletedOptions = explode( ',', $data['Deleted'] );
+
+ //Debug::show( $deletedOptions );
+
+ // store default, etc
+ foreach( $fieldSet as $option ) {
+
+ //Debug::show( $option );
+
+ if( $deletedOptions && array_search( $option->ID, $deletedOptions ) !== false ) {
+ $option->delete();
+ continue;
+ }
+
+ if( $data[$option->ID] ) {
+ $option->setField( 'Default', $option->ID == $data['Default'] );
+ $option->populateFromPostData( $data[$option->ID] );
+ }
+
+ unset( $data[$option->ID] );
+ }
+
+ // Debug::show( $data );
+
+ foreach( $data as $tempID => $optionData ) {
+
+ $optionNumber = 0;
+
+ if( !$tempID || !is_array( $optionData ) || empty( $optionData ) || !preg_match('/^_?\d+$/', $tempID ) )
+ continue;
+
+ // what will we name the new option?
+ $newOption = new EditableRadioOption();
+ $newOption->Name = sprintf( 'option%d', $optionNumber++ );
+ $newOption->ParentID = $this->ID;
+ $newOption->setField( 'Default', $tempID == $data['Default'] );
+ $newOption->populateFromPostData( $optionData );
+
+ // $mail .= "NEW: " . $optionData['Title'] . "\n";
+
+ if( Director::is_ajax() ) {
+ $fieldID = $this->ID;
+ $fieldEditorName = $this->editor ? $this->editor->Name() : 'Fields';
+ $prefix = $fieldEditorName . '[' . $fieldID . ']';
+ $newID = $newOption->ID;
+ $newSort = $newOption->Sort;
+ echo "\$('". $fieldEditorName . "[$fieldID]').updateOption('$prefix','$tempID','$newID','$newSort');";
+ }
+
+ if( !$newOption->Title )
+ user_error('Added blank option '.$tempID, E_USER_ERROR);
+ }
+ }
+
+ function DefaultOption() {
+ $defaultOption = 0;
+
+ foreach( $this->Options() as $option ) {
+ if( $option->getField('Default') )
+ return $defaultOption;
+ else
+ $defaultOption++;
+ }
+
+ return -1;
+ }
+
+ function getFormField() {
+ return $this->createField();
+ }
+
+ function getFilterField() {
+ return $this->createField( true );
+ }
+
+ function createField( $asFilter = false ) {
+ $optionSet = $this->Options();
+ $options = array();
+ $defaultOption = '';
+
+ if( $asFilter )
+ $options['-1'] = '(Any)';
+
+ // $defaultOption = '-1';
+
+ foreach( $optionSet as $option ) {
+ $options[$option->Title] = $option->Title;
+ if( $option->getField('Default') && !$asFilter ) $defaultOption = $option->Title;
+ }
+
+ // return radiofields
+ return new OptionsetField($this->Name, $this->Title, $options, $defaultOption);
+ }
+
+ function prepopulate( $value ) {
+
+ $options = $this->Options();
+
+ $paramMap = $this->parsePrepopulateValue( $value );
+
+ // find options and add them
+ $optionNumber = 0;
+ foreach( $paramMap['Options'] as $newOption ) {
+ if( preg_match( '/([^:]+)[:](.*)/', $newOption, $match ) ) {
+ $newOptionValue = $match[1];
+ $newOptionTitle = $match[2];
+
+ $newOptionTitle = preg_replace('/__/', ' ', $newOptionTitle );
+
+ $newOption = $this->createOption(
+ 'option' . (string)$optionNumber,
+ $newOptionTitle,
+ 'new-' . (string)$optionNumber,
+ $newOption['Sort'],
+ $optionNumber == 1,
+ false
+ );
+
+ $optionNumber++;
+ $options->addWithoutWrite( $newOption );
+ }
+ }
+ }
+
+protected function createOption( $name, $title, $id, $sort = 0, $isDefault = false ) {
+ $newOption = new EditableRadioOption();
+ $newOption->Name = $name;
+ $newOption->Title = $title;
+ $newOption->ID = $id;
+ $newOption->Sort = $sort;
+ $newOption->setField('Default', $isDefault ? '1' : '0');
+
+ return $newOption;
+ }
+
+ function TemplateOption() {
+ $option = new EditableRadioOption();
+ $option->ParentID = $this->ID;
+ return $option->EditSegment();
+ }
+ }
+?>
diff --git a/code/editor/EditableRadioOption.php b/code/editor/EditableRadioOption.php
new file mode 100755
index 0000000..2c40464
--- /dev/null
+++ b/code/editor/EditableRadioOption.php
@@ -0,0 +1,84 @@
+readonly = true;
+ return $this->EditSegment();
+ }
+
+ function isReadonly() {
+ return $this->readonly;
+ }
+
+ static $default_sort = "Sort";
+
+ // add required here?
+ static $db = array(
+ "Name" => "Varchar",
+ "Title" => "Varchar",
+ "Default" => "Boolean",
+ "Value" => "Varchar",
+ "Sort" => "Int"
+ );
+ static $has_one = array(
+ "Parent" => "EditableRadioField",
+ );
+
+ static $singular_name = 'Radio option';
+ static $plural_name = 'Radio options';
+
+ function EditSegment() {
+ return $this->renderWith('EditableFormFieldOption');
+ }
+
+ function TitleField() {
+ return new TextField( "Fields[{$this->ParentID}][{$this->ID}][Title]", null, $this->Title );
+ }
+
+ function Name() {
+ return "Fields[{$this->ParentID}][{$this->ID}]";
+ }
+
+ function populateFromPostData( $data ) {
+ $this->Title = $data['Title'];
+ $this->Sort = $data['Sort'];
+ $this->write();
+ }
+
+ function Option() {
+ // return new radio field
+ /*$title = Convert::raw2att( $this->Title );
+
+ $default = "";
+
+ if( $this->getField('Default') )
+ $default = '+';
+ else
+ $default = '-';
+
+ //Debug::show($this);
+ return ' ';*/
+
+ return $this->EditSegment();
+ }
+
+ function DefaultSelect() {
+ $disabled = ($this->readonly) ? " disabled=\"disabled\"" : '';
+
+ if($this->Parent()->getField('Default') == $this->ID) {
+ $default = " checked=\"checked\"";
+ } else {
+ $default = '';
+ }
+
+ return " ParentID}][Default]\" value=\"{$this->ID}\"".$disabled.$default." />";
+ }
+}
+?>
\ No newline at end of file
diff --git a/code/editor/EditableTextField.php b/code/editor/EditableTextField.php
new file mode 100755
index 0000000..2fa313f
--- /dev/null
+++ b/code/editor/EditableTextField.php
@@ -0,0 +1,92 @@
+ "Int",
+ "MinLength" => "Int",
+ "MaxLength" => "Int",
+ "Rows" => "Int"
+ );
+
+ static $singular_name = 'Text field';
+ static $plural_name = 'Text fields';
+
+ function __construct( $record = null, $isSingleton = false ) {
+ $this->Size = 32;
+ $this->MinLength = 1;
+ $this->MaxLength = 32;
+ $this->Rows = 1;
+ parent::__construct( $record, $isSingleton );
+ }
+
+ function ExtraOptions() {
+
+ // eventually replace hard-coded "Fields"?
+ $baseName = "Fields[$this->ID]";
+
+ $extraFields = new FieldSet(
+ new TextField($baseName . "[Size]", _t('EditableTextField.TEXTBOXLENGTH', 'Length of text box'), (string)$this->Size),
+ new FieldGroup(_t('EditableTextField.TEXTLENGTH', 'Text length'),
+ new TextField($baseName . "[MinLength]", "", (string)$this->MinLength),
+ new TextField($baseName . "[MaxLength]", " - ", (string)$this->MaxLength)
+ ),
+ new TextField($baseName . "[Rows]", _t('EditableTextField.NUMBERROWS', 'Number of rows'), (string)$this->Rows)
+ );
+
+ foreach( parent::ExtraOptions() as $extraField )
+ $extraFields->push( $extraField );
+
+ if( $this->readonly )
+ $extraFields = $extraFields->makeReadonly();
+
+ return $extraFields;
+ }
+
+ function populateFromPostData( $data ) {
+
+ $this->Size = !empty( $data['Size'] ) ? $data['Size'] : 32;
+ $this->MinLength = !empty( $data['MinLength'] ) ? $data['MinLength'] : 1;
+ $this->MaxLength = !empty( $data['MaxLength'] ) ? $data['MaxLength'] : 32;
+ $this->Rows = !empty( $data['Rows'] ) ? $data['Rows'] : 1;
+ parent::populateFromPostData( $data );
+ }
+
+ function getFormField() {
+ return $this->createField();
+ }
+
+ function getFilterField() {
+ return $this->createField( true );
+ }
+
+ function createField( $asFilter = false ) {
+ if( $this->Rows == 1 )
+ return new TextField( $this->Name, $this->Title, ( $asFilter ) ? "" : $this->getField('Default'), ( $this->Size && $this->Size > 0 ) ? $this->Size : null );
+ else
+ return new TextareaField( $this->Name, $this->Title, $this->Rows, $this->Size, ( $asFilter ) ? "" : $this->getField('Default') );
+ }
+
+ /**
+ * Populates the default fields.
+ */
+ function DefaultField() {
+ $disabled = '';
+ if( $this->readonly ){
+ $disabled = " disabled=\"disabled\"";
+ } else {
+ $disabled = '';
+ }
+ if( $this->Rows == 1 ){
+ return ''._t('EditableTextField.DEFAULTTEXT', 'Default Text').'
';
+ }else{
+ return ''._t('EditableTextField.DEFAULTTEXT', 'Default Text').'
';
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/code/editor/FieldEditor.php b/code/editor/FieldEditor.php
new file mode 100755
index 0000000..d837194
--- /dev/null
+++ b/code/editor/FieldEditor.php
@@ -0,0 +1,199 @@
+readonly;
+ }
+
+ function performReadonlyTransformation() {
+ $this->readonly = true;
+ return $this;
+ }
+
+ function makeReadonly() {
+ return $this->performReadonlyTransformation();
+ }
+
+ function FieldHolder() {
+ return $this->renderWith("FieldEditor");
+ }
+
+ function Fields() {
+ Requirements::css(SAPPHIRE_DIR . "/css/FieldEditor.css");
+ Requirements::javascript(SAPPHIRE_DIR . "/javascript/FieldEditor.js");
+
+ $relationName = $this->name;
+
+ $fields = $this->form->getRecord()->$relationName();
+
+ if( $this->readonly ) {
+ $readonlyFields = new DataObjectSet();
+
+ foreach( $fields as $field ) {
+ $field->setEditor( $this );
+ $readonlyFields->push( $field->makeReadonly() );
+ }
+
+ $fields = $readonlyFields;
+ }
+
+ return $fields;
+ }
+
+ function saveInto(DataObject $record) {
+
+ $name = $this->name;
+ $fieldSet = $record->$name();
+
+ $record->EmailTo = $_REQUEST[$name]['EmailTo'];
+ $record->EmailOnSubmit = isset( $_REQUEST[$name]['EmailOnSubmit'] ) ? "1" : "0";
+ $record->SubmitButtonText = $_REQUEST[$name]['SubmitButtonText'];
+
+ // 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;
+ }
+
+ // write the new fields to the database
+ if($_REQUEST[$name]){
+ foreach( array_keys( $_REQUEST[$name] ) as $newEditableID ) {
+ $newEditableData = $_REQUEST[$name][$newEditableID];
+
+ // `ParentID`=0 is for the new page
+ $editable = DataObject::get_one( 'EditableFormField', "(`ParentID`='{$record->ID}' OR `ParentID`=0) AND `EditableFormField`.`ID`='$newEditableID'" );
+
+ // check if we are updating an existing field
+ if( $editable && isset($missingFields[$editable->ID]))
+ unset( $missingFields[$editable->ID] );
+
+ // create a new object
+ // this should now be obsolete
+ if(!$editable && !empty($newEditableData['Type']) && class_exists($newEditableData['Type'])) {
+ $editable = new $newEditableData['Type']();
+ $editable->ID = 0;
+ $editable->ParentID = $record->ID;
+
+ if(!is_subclass_of($editable, 'EditableFormField')) {
+ $editable = null;
+ }
+ }
+
+ if($editable) {
+ if($editable->ParentID == 0) {
+ $editable->ParentID = $record->ID;
+ }
+ $editable->populateFromPostData($newEditableData);
+ //$editable->write();
+ }
+ }
+ }
+
+ // remove the fields not saved
+ foreach($missingFields as $removedField) {
+ if(is_numeric($removedField->ID)) $removedField->delete();
+ }
+
+ if($record->hasMethod('customFormSave')) {
+ $record->customFormSave( $_REQUEST[$name], $record );
+ }
+ //$record->writeWithoutVersion();
+
+ if($record->hasMethod( 'processNewFormFields')) {
+ $record->processNewFormFields();
+ }
+ }
+
+ /*function addNewField( $newField ) {
+ $newField->ParentID = $this->ID;
+ $newField->write();
+ return $this->renderWith("FieldEditor");
+ }*/
+
+ function addfield() {
+ // get the last field in this form editor
+ $parentID = $this->form->getRecord()->ID;
+ $lastField = DataObject::get('EditableFormField', "`ParentID`='$parentID'", "`Sort` DESC", null, 1 );
+
+ $nextSort = 1;
+
+ // the new sort value is the value of the last sort + 1 if a field exists
+ if( $lastField )
+ $nextSort += $lastField->Sort;
+
+ $className = "Editable" . ucfirst($_REQUEST['Type']);
+ $name = $this->name;
+ if(is_subclass_of($className, "EditableFormField")) {
+ $e = new $className();
+ // $fields = $this->form->getRecord()->$name()->Count();
+ // $e->Name = $this->name . "[NewFields][]";
+ // Debug::show($fields);
+
+ /*if( $this->form->getRecord()->hasMethod('addField') )
+ $this->form->getRecord()->addField( $e );
+ else*/
+ $e->ParentID = $this->form->getRecord()->ID;
+
+ //Debug::show($e);
+ $e->write();
+ //$e->ID = "new-" . ( $_REQUEST['NewID'] + 1 );
+ $e->Name = $e->class . $e->ID;
+ $e->write();
+
+ return $e->EditSegment();
+ } else {
+ user_error("FieldEditor::addfield: Tried to create a field of class '$className'", E_USER_ERROR);
+ }
+ }
+
+ function adddropdownfield() {
+ return $this->addNewField( new EditableDropdown() );
+ }
+
+ function addcheckboxfield() {
+ return $this->addNewField( new EditableCheckbox() );
+ }
+
+ protected $haveFormOptions = true;
+
+ function setHaveFormOptions($bool){
+ $this->haveFormOptions = $bool;
+ }
+
+ function getHaveFormOptions(){
+ return $this->haveFormOptions;
+ }
+
+ function FormOptions() {
+ if($this->haveFormOptions){
+ $fields = new FieldSet(
+ new EmailField( "{$this->name}[EmailTo]", _t('FieldEditor.EMAILSUBMISSION', 'Email submission to:'), $this->form->getRecord()->EmailTo ),
+ new CheckboxField( "{$this->name}[EmailOnSubmit]", _t('FieldEditor.EMAILONSUBMIT', 'Email form on submit:'), $this->form->getRecord()->EmailOnSubmit )
+ );
+
+ if( $this->form->getRecord()->hasMethod( 'customFormActions' ) ) {
+ $newFields = $this->form->getRecord()->customFormActions( $this->readonly );
+
+ foreach( $newFields as $newField ) {
+ $newField->setName( "{$this->name}[{$newField->Name()}]" );
+ $fields->push( $newField );
+ }
+ }
+ if( $this->readonly )
+ $fields = $fields->makeReadonly();
+
+ return $fields;
+ }
+
+ }
+}
+?>
\ No newline at end of file
diff --git a/code/editor/SubmittedFileField.php b/code/editor/SubmittedFileField.php
new file mode 100755
index 0000000..90d09f5
--- /dev/null
+++ b/code/editor/SubmittedFileField.php
@@ -0,0 +1,13 @@
+ "File"
+ );
+
+}
+?>
\ No newline at end of file
diff --git a/code/editor/SubmittedForm.php b/code/editor/SubmittedForm.php
new file mode 100755
index 0000000..8f9e8ec
--- /dev/null
+++ b/code/editor/SubmittedForm.php
@@ -0,0 +1,24 @@
+ "Member",
+ "Parent" => "UserDefinedForm",
+ );
+
+ static $db = array(
+ "Recipient" => "Varchar"
+ );
+
+ static $has_many = array(
+ "FieldValues" => "SubmittedFormField"
+ );
+
+ function SubmitTime() {
+ return $this->Created;
+ }
+}
+?>
\ No newline at end of file
diff --git a/code/editor/SubmittedFormField.php b/code/editor/SubmittedFormField.php
new file mode 100755
index 0000000..8b0cb57
--- /dev/null
+++ b/code/editor/SubmittedFormField.php
@@ -0,0 +1,18 @@
+ "Varchar",
+ "Value" => "Text",
+ "Title" => "Varchar"
+ );
+
+ static $has_one = array(
+ "Parent" => "SubmittedForm"
+ );
+}
+?>
\ No newline at end of file
diff --git a/code/editor/SubmittedFormReportField.php b/code/editor/SubmittedFormReportField.php
new file mode 100755
index 0000000..9503c57
--- /dev/null
+++ b/code/editor/SubmittedFormReportField.php
@@ -0,0 +1,26 @@
+form->Form();*/
+ }
+
+ function Field() {
+ Requirements::css(SAPPHIRE_DIR . "/css/SubmittedFormReportField.css");
+
+ return $this->renderWith("SubmittedFormReportField");
+ }
+
+ function Submissions() {
+ return $this->form->getRecord()->Submissions();
+ }
+}
+?>
\ No newline at end of file
diff --git a/css/FieldEditor.css b/css/FieldEditor.css
new file mode 100755
index 0000000..98398ab
--- /dev/null
+++ b/css/FieldEditor.css
@@ -0,0 +1,202 @@
+* {
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 10px;
+}
+
+div.FieldEditor {
+ border: 1px solid #CCCCCC;
+ padding: 0px;
+}
+
+div.FieldEditor ul.Menu {
+ margin: 0;
+ padding: 3px;
+ background-color: #eee;
+ position: relative;
+}
+
+ div.FieldEditor div.FormOptions div.CompositeField {
+ margin-left: 4em;
+ }
+
+ div.FieldEditor div.FormOptions label.left {
+ position: relative;
+ }
+
+ div.FieldEditor div.FormOptions label.right,
+ div.FieldEditor div.FormOptions label.CustomActionLabel {
+ margin-left: 0em;
+ float: none;
+ line-height: auto;
+ display: inline;
+ }
+
+ div.FieldEditor input.checkbox {
+ width: auto !important;
+ }
+
+ div.FieldEditor ul.Menu li {
+ background-color: transparent;
+ }
+
+ div.FieldEditor div.FieldList {
+ background-color: white;
+ border-bottom: 1px solid #CCCCCC;
+ border-top: 1px solid #CCCCCC;
+ height: 75%;
+ /*overflow-y: scroll;*/
+ }
+
+ div.FieldEditor div.FormOptions {
+ background-color: #EEEEEE;
+ position: relative;
+ float: left;
+ width:100%;
+ }
+
+ div.FieldEditor div.FormOptions div.field {
+ position: relative;
+ }
+
+ div.FieldEditor div.FormOptions div.optionset li {
+ display: block;
+ }
+
+ div.FieldEditor div.FormOptions div.optionset li label {
+ float: none !important;
+ }
+
+.DragEditable {
+ border: 0;
+}
+
+div.FieldEditor ul li {
+ display: inline;
+}
+
+div.FieldEditor ul a {
+ padding: 3px;
+}
+
+div.FieldEditor ul a:hover {
+ background-color: #CCCCCC;
+}
+
+/*********************************************************************
+ * EditableFormField - Default class for all editable form fields
+ ********************************************************************/
+
+div.EditableFormField {
+ padding: 3px;
+ border-top: 1px dashed #CCCCCC;
+}
+
+div.EditableFormField.mouseOver {
+ background-color: #f3ef9d;
+}
+
+ div.EditableFormField div.hidden {
+ display: none;
+ margin: 0px;
+ }
+
+ div.EditableFormField div.FieldInfo {
+ margin: 0px;
+ padding-right: -3px;
+ }
+
+ div.EditableFormField div.FieldInfo * {
+ display: inline;
+ margin-right: 3px;
+ margin-left: 0px;
+ vertical-align: middle;
+ }
+
+ div.EditableFormField div.FieldInfo input,
+ div.EditableFormField div.FieldDefault input {
+ width: 250px;
+ margin-left: 0px;
+ }
+
+ div.EditableFormField div.ExtraOptions {
+ display: none;
+ margin: 3px 0px 3px 57px;
+ background-color: #EEEEEE;
+ /* IE has background issues without this */
+ /*position: relative;*/
+ padding: 3px;
+ }
+
+/*********************************************************************
+ * EditableDateField
+ ********************************************************************/
+
+div.EditableDateField div.FieldDefault input {
+ width: 2em;
+}
+
+ div.EditableDateField div.FieldDefault input.year {
+ width: 4em;
+ }
+
+/*********************************************************************
+ * EditableEmailField
+ ********************************************************************/
+
+/*********************************************************************
+ * EditableDropdown
+ ********************************************************************/
+
+#right #Form_EditForm div.EditableMultiOptionFormField div.FieldDefault ul.EditableDropdownOptions {
+ border: solid 1px #7F9DB9;
+ /* IE */
+ margin: 0px;
+ /* display: none; */
+ display: block;
+ position: relative;
+}
+
+#right #Form_EditForm div.EditableDropdown select {
+ margin-bottom: 0px;
+}
+
+#right #Form_EditForm div.EditableDropdown select,
+#right #Form_EditForm div.EditableMultiOptionFormField ul.EditableDropdownOptions {
+ width: 250px;
+ padding: 3px;
+}
+
+ #right #Form_EditForm li.EditableFormFieldOption {
+ display: block;
+ padding: 2px 0px;
+ }
+
+ #right #Form_EditForm li.EditableFormFieldOption * {
+ vertical-align: middle;
+ display: inline;
+ }
+
+ #right #Form_EditForm li.EditableFormFieldOption a {
+ width: 16px;
+ }
+
+ #right #Form_EditForm li.EditableFormFieldOption input.text {
+ width: 170px !important;
+ }
+
+ #right #Form_EditForm li.EditableFormFieldOption input.radio,
+ #right #Form_EditForm li.EditableFormFieldOption input.checkbox {
+ width: 20px;
+ }
+
+ #right #Form_EditForm li.AddDropdownOption {
+ border-top: dashed 1px #7F9DB9;
+ padding-left: 37px;
+ height: auto;
+ }
+
+ /* Need first rule in order to overwrite */
+ #right #Form_EditForm li.AddDropdownOption input.text {
+ width: 170px !important;
+ margin-left: 0px;
+ }
\ No newline at end of file
diff --git a/javascript/FieldEditor.js b/javascript/FieldEditor.js
new file mode 100755
index 0000000..29cd0e0
--- /dev/null
+++ b/javascript/FieldEditor.js
@@ -0,0 +1,555 @@
+FieldEditor = Class.create();
+FieldEditor.applyTo('div.FieldEditor');
+FieldEditor.prototype = {
+ initialize: function() {
+ FieldEditorField.applyToChildren(this, 'div.EditableFormField');
+ FieldEditorHeadingField.applyToChildren(this, 'div.EditableFormHeading');
+ FieldEditorRadioField.applyToChildren(this, 'div.EditableRadioField');
+ FieldEditorCheckboxGroupField.applyToChildren(this, 'div.EditableCheckboxGroupField');
+ FieldEditorDropdown.applyToChildren(this, 'div.EditableDropdown');
+ FieldEditorEmailField.applyToChildren(this, 'div.EditableEmailField');
+ FieldEditorTextField.applyToChildren(this, 'div.EditableTextField');
+
+ if( !Element.hasClassName( this, 'readonly' ) ) {
+ Sortable.create('Fields_fields', {tag: 'div', handle:'handle'});
+ $('Form_EditForm').observeMethod('BeforeSave', this.beforeSave.bind(this));
+ }
+
+ },
+ sortFields: function() {
+ var fieldEditor = $('Fields_fields');
+
+ if(fieldEditor) {
+
+ var i, j, div, field, editables = fieldEditor.childNodes;
+
+ for( i = 0; div = editables[i]; i++ ) {
+ var fields = div.getElementsByTagName('input');
+ /*fields[fields.length - 1].value = i;*/
+ for( j = 0; field = fields.item(j); j++ ) {
+ if( field.name == div.id + '[Sort]' ) {
+ field.value = i;
+ }
+ }
+ }
+ }
+ },
+ beforeSave: function() {
+ var fieldEditor = $('Fields_fields');
+
+ if(fieldEditor) {
+ this.sortFields();
+
+ var children = $('Fields_fields').childNodes;
+
+ for( var i = 0; i < children.length; ++i ) {
+ var child = children[i];
+
+ if( child.beforeSave )
+ child.beforeSave();
+ }
+ }
+ },
+ deleteOption: function( optionToRemove ) {
+ this.getElementsByTagName('div')[0].removeChild( optionToRemove );
+ }
+}
+
+FieldEditorField = Class.create();
+
+FieldEditorField.prototype = {
+ initialize: function() {
+ var fieldInfoDiv = this.findDescendant( 'div', 'FieldInfo' );
+
+ this.titleField = this.findDescendant( 'input', 'text', element );
+
+ this.titleField.onchange = this.changeTitle.bind(this);
+ this.titleField.onblur = this.changeTitle.bind(this);
+ this.titleField.onfocus = this.focusTitle.bind(this);
+
+ this.titleField.onchange();
+
+ var links = fieldInfoDiv.getElementsByTagName('a');
+ this.toggler = this.findDescendant( 'a', 'toggler' );
+ this.fieldInfo = this.getElementsByTagName('div')[0];
+
+
+ this.toggler.onclick = this.toggle.bind(this);
+ this.extraOptions = this.getExtraOptions();
+ this.visible = false;
+ this.deleteButton = this.findDescendant('a', 'delete');
+
+ //this.style.height = "auto";
+
+ if( this.deleteButton )
+ this.deleteButton.onclick = this.confirmDelete.bind(this);
+ },
+ toggle: function() {
+ // this.parentNode.autoSize();
+
+ if( this.visible )
+ this.hide();
+ else
+ this.show();
+
+ this.fieldInfo.style.display = 'block';
+
+ return false;
+ },
+ show: function() {
+ /*this.style.height = "";
+ this.style.overflow = "";*/
+
+ if( this.selectedOption )
+ this.selectedOption.checked = true;
+
+ this.visible = true;
+ // var extraOptions = this.getExtraOptions();
+ // if( this.extraOptions )
+ this.extraOptions.style.display = 'block';
+ },
+ hide: function() {
+
+ this.visible = false;
+ // var extraOptions = this.getExtraOptions();
+ //if( this.extraOptions )
+ this.extraOptions.style.display = 'none';
+ },
+ getExtraOptions: function() {
+ var extraOptions = this.findDescendant('div', 'ExtraOptions');
+
+ if( extraOptions.parentNode != this )
+ alert("Found extra options but not this parent (" + this.id + ")");
+
+ return extraOptions;
+ },
+ confirmDelete: function() {
+ if( confirm( 'Are you sure you want to delete this field from the form?' ) )
+ this.parentNode.parentNode.deleteOption( this );
+
+ return false;
+ },
+ findDescendant: function( tag, clsName, element ) {
+
+ if( !element )
+ element = this;
+
+ var descendants = element.getElementsByTagName(tag);
+
+ for( var i = 0; i < descendants.length; i++ ) {
+ var el = descendants[i];
+ // alert(el.tagName + ' ' + el.className);
+
+ if( tag.toUpperCase() == el.tagName && el.className.indexOf( clsName ) != -1 )
+ return el;
+ }
+
+ return null;
+ },
+ focusTitle: function() {
+ if( this.titleField && this.titleField.value == this.titleField.title )
+ this.titleField.value = '';
+ },
+ changeTitle: function() {
+ if( this.titleField && this.titleField.value == '' )
+ this.titleField.value = this.titleField.title;
+ }
+}
+
+FieldEditorHeadingField = Class.extend('FieldEditorField');
+
+FieldEditorHeadingField.prototype = {
+ initialize: function() {
+ this.FieldEditorField.initialize();
+ }
+}
+
+FieldEditorEmailField = Class.extend('FieldEditorField');
+
+
+FieldEditorEmailField.prototype = {
+ initialize: function() {
+ this.extraOptions = this.getExtraOptions();
+ this.defaultText = this.getDefaultText();
+
+ this.FieldEditorField.initialize();
+ },
+ getDefaultText: function() {
+ var defaultField = this.getDefaultField();
+ if(defaultField) {
+ var j, nestedChild, nestedChildren = defaultField.childNodes;
+ for( j=0; nestedChild = nestedChildren[j]; j++) {
+ if (nestedChild.className == 'defaultText' )
+ {
+ return nestedChild;
+ }
+ }
+ }
+ },
+ getDefaultField: function() {
+
+ var i, child, children = this.getElementsByTagName('div');
+ for( i = 0; child = children[i]; i++){
+ if(child.className == 'FieldDefault'){
+ return child;
+ }
+ }
+ }
+}
+
+
+FieldEditorTextField = Class.extend('FieldEditorField');
+FieldEditorTextField.prototype = {
+ initialize: function() {
+ this.FieldEditorField.initialize();
+ this.defaultText = this.getDefaultText();
+ this.numRows = this.extraOptions.getElementsByTagName('input')[3];
+ if(this.numRows) {
+ this.numRows.onchange = this.changedRows.bind(this);
+ this.oldNumRows = eval(this.numRows.value);
+ }
+
+ },
+ changedRows: function() {
+ var newNumRows = eval(this.numRows.value);
+
+ // TODO Show that the field is actually longer than 5 rows
+ if( newNumRows > 5 )
+ newNumRows == 5;
+
+ if( this.oldNumRows == newNumRows )
+ return;
+
+ if( newNumRows < 1 )
+ newNumRows = 1;
+
+ // resize/convert the textarea
+ var newType = '';
+
+ if( newNumRows == 1 )
+ newType = 'input';
+ else
+ newType = 'textarea'
+
+ var newDefaultText = document.createElement(newType);
+ newDefaultText.className = this.defaultText.className;
+ newDefaultText.value = this.defaultText.value;
+ newDefaultText.id = this.defaultText.id;
+ newDefaultText.name = this.defaultText.name;
+
+ if( newDefaultText.rows )
+ newDefaultText.rows = newNumRows;
+
+ //Does not work any more
+ //this.replaceChild( newDefaultText, this.defaultText );
+
+ //instead, using the following code
+ var defaultField = this.getDefaultField();
+ defaultField.replaceChild(newDefaultText, this.defaultText);
+
+ //keep other codes.
+ this.defaultText = newDefaultText;
+ this.oldNumRows = newNumRows;
+ },
+ getDefaultText: function() {
+ var defaultField = this.getDefaultField();
+
+ if(defaultField) {
+ var j, nestedChild, nestedChildren = defaultField.childNodes;
+ for( j=0; nestedChild = nestedChildren[j]; j++) {
+
+ if (nestedChild.className == 'defaultText' )
+ {
+ return nestedChild;
+ }
+ }
+ }
+ },
+ getDefaultField: function() {
+ var i, child, children = this.getElementsByTagName('div');
+ for( i = 0; child = children[i]; i++){
+ if(child.className == 'FieldDefault'){
+ return child.getElementsByTagName('div')[0];
+ }
+ }
+ }
+}
+
+/**
+ * This should extend FieldEditorField
+ */
+FieldEditorRadioField = Class.extend('FieldEditorField');
+
+FieldEditorRadioField.prototype = {
+ initialize: function() {
+ this.FieldEditorField.initialize();
+
+ this.hiddenFields = this.findDescendant( 'div', 'hidden' );
+
+ var dropdownBox = this.findDescendant( 'div', 'EditableDropdownBox' );
+
+ this.optionList = dropdownBox.getElementsByTagName('ul')[0];
+ var options = this.optionList.getElementsByTagName('li');
+
+ if( options && options.length > 0 ) {
+ this.addOptionField = options[options.length - 1];
+
+ if( typeof this.addOptionField != 'undefined' && this.addOptionField.className != "AddDropdownOption" )
+ this.addOptionField = null;
+
+ // bind each option's delete link
+ for( var i = 0; i < options.length - 1; i++ ) {
+ var option = options[i];
+
+ var links = option.getElementsByTagName('a');
+
+ links[0].onclick = this.removeOption.bindAsEventListener(this);
+ }
+ }
+
+ // Bind method to add option at the bottom of the list
+ if( this.addOptionField ) {
+ this.addOptionLink = this.addOptionField.getElementsByTagName('a')[0];
+ this.addOptionTitle = this.addOptionField.getElementsByTagName('input')[0];
+ this.addOptionLink.onclick = this.addOption.bind(this);
+ }
+
+ if( !Element.hasClassName( $('Fields'), 'readonly' ) ) {
+ Sortable.create(this.optionList.id,{handle:'handle',tag:'li',only:'EditableFormFieldOption'});
+ }
+ this.FieldEditorField.initialize();
+
+ // find the Delete field
+ var hiddenFields = this.getElementsByTagName('input');
+
+ for( var i = 0; i < hiddenFields.length; i++ ) {
+ var field = hiddenFields[i];
+ if( field.name.indexOf('[Deleted\]' ) != -1 )
+ this.deletedOptions = field;
+ }
+
+ this.selectedOption = null;
+
+ $('Form_EditForm').observeMethod('BeforeSave', this.beforeSave.bind(this));
+ },
+ firstElement: function( el ) {
+
+ var node = el.firstChild;
+
+ while( !node.tagName )
+ node = node.nextSibling;
+
+ return node;
+ },
+ createOption: function( title, id, selected ) {
+ var templateNode = this.firstElement( this.hiddenFields );
+ var newOptionNode = templateNode.cloneNode( true );
+
+ var newNodeChildren = newOptionNode.childNodes;
+
+ for( var i = 0; i < newNodeChildren.length; i++ ) {
+
+ var child = newNodeChildren[i];
+
+ if( !child.tagName )
+ continue;
+
+ // input elements
+ if( child.tagName.toLowerCase() == 'input' ) {
+
+ if( child.className == 'text' ) {
+ child.name = this.id + '[' + id + '][Title]';
+ child.value = title;
+ } else if( child.type == 'checkbox' )
+ child.name = this.id + '[' + id + '][Default]';
+ else if( child.type == 'radio' ) {
+ child.value = id;
+ } else if( child.type == 'hidden' ) {
+ child.name = this.id + '[' + id + '][Sort]';
+ child.value = -1;
+ }
+ } else if ( child.tagName.toLowerCase() == 'a' ) {
+ child.onclick = this.removeOption.bindAsEventListener(this);
+ }
+ }
+
+ this.optionList.insertBefore( newOptionNode, this.addOptionField );
+ },
+ removeOption: function( event ) {
+
+ var target = event.srcElement;
+
+ if( !target )
+ target = event.target;
+
+ var entry = target.parentNode.parentNode;
+ var id = entry.id;
+
+ if( !id.match( '/^[0-9]+$/' ) ) {
+ if( this.deletedOptions.value )
+ this.deletedOptions.value += ',';
+
+ this.deletedOptions.value += id;
+ }
+
+ // remove the child from the options
+ this.optionList.removeChild( entry );
+
+ // remove the child from the dropdown
+ /*for( var i = 0; i < this.dropdown.length; i++ ) {
+ if( this.dropdown.options[i].text == title ) {
+ this.dropdown.remove(i);
+ return false;
+ }
+ }*/
+
+ if( !Element.hasClassName( $('Fields'), 'readonly' ) )
+ Sortable.create(this.optionList.id,{handle:'handle',tag:'li',only:'EditableFormFieldOption'});
+
+ // return false so it doesn't follow the link
+ return false;
+ },
+ addOption: function() {
+ if( this.addOptionTitle.value.length == 0 )
+ return false;
+
+ // The IDs come from the database and are the ID of the actual record
+ // client-side, we will need a unique identifier that can be differentiated
+ // from the actual database IDs, unless we just drop all records and
+ // recreate them
+ var newID = '_' + this.optionList.childNodes.length;
+
+ this.createOption( this.addOptionTitle.value, newID, this.optionList.childNodes.length == 0 );
+
+ if( !Element.hasClassName( $('Fields'), 'readonly' ) )
+ Sortable.create(this.optionList.id,{handle:'handle',tag:'li',only:'EditableFormFieldOption'});
+
+ this.addOptionTitle.value = '';
+
+ return false;
+ },
+ beforeSave: function() {
+ this.sortOptions();
+ },
+ sortOptions: function() {
+ var inputTags = this.optionList.getElementsByTagName('input');
+
+ var i,item,sort=0;
+ for(i=0;item=inputTags[i];i++) {
+ if(item.name.match(/\[Sort\]$/) ) {
+ item.value = sort++;
+ }
+ }
+ },
+ selectOption: function(newOption) {
+
+ if( this.selectedOption )
+ this.selectedOption.checked = false;
+
+ newOption.checked = true;
+ this.selectedOption = newOption;
+ },
+ selectOptionEvent: function(event) {
+ if(event.srcElement)
+ this.selectOption(event.srcElement);
+ else
+ this.selectOption(event.target);
+ },
+ updateOption: function( prefix, tempID, newID, newSort ) {
+ var options = this.optionList.childNodes;
+
+ for( var i = 0; i < options.length; i++ ) {
+ var option = options[i];
+
+ var fields = option.getElementsByTagName('input');
+
+ for( var j = 0; j < fields.length; j++ ) {
+ var field = fields[j];
+
+ var oldPrefix = prefix + '[' + tempID + ']';
+ var newPrefix = prefix + '[' + newID + ']';
+
+ if( field.name.indexOf( oldPrefix ) == 0 ) {
+
+ if( field.name.match( /\[Sort\]$/ ) )
+ field.value = newSort;
+
+ // rename the field
+ field.name = newPrefix + field.name.substring( oldPrefix.length );
+
+ } else if( field.name == prefix + '[Default]' ) {
+ field.value = newID;
+ }
+ }
+ }
+ }
+}
+
+FieldEditorCheckboxGroupField = Class.extend('FieldEditorRadioField');
+
+FieldEditorDropdown = Class.extend('FieldEditorRadioField');
+
+Behaviour.register(
+ {
+ 'div.FieldEditor ul.Menu li a': {
+
+
+ urlForFieldMethod: function(methodName) {
+ return this.ownerForm().action + '/field/Fields/' + methodName + '?NewID=' + this.numNewFields;
+ },
+ ownerForm: function() {
+ var f = this.parentNode;
+ while(f && f.tagName.toLowerCase() != 'form') f = f.parentNode;
+ return f;
+ },
+
+ onclick: function() {
+ // get the ID of the field editor here
+
+ if( Element.hasClassName( $('Fields'), 'readonly' ) )
+ return false;
+
+ action = this.urlForFieldMethod("addfield") + "&Type=" + this.id + ($('SecurityID') ? '&SecurityID=' + $('SecurityID').value : '');;
+
+ statusMessage('Adding new field' );
+
+ new Ajax.Request(action, {
+ method: 'get',
+ onFailure: reportError,
+ onSuccess: this.appendNewField.bind(this)
+ });
+
+ return false;
+ },
+
+ appendNewField: function(response) {
+ this.numNewFields++;
+
+ var el = document.createElement('div');
+ el.innerHTML = response.responseText;
+
+ var i=0;
+ while(!el.childNodes[i].tagName) i++;
+ var newField = el.childNodes[i];
+ $('Fields_fields').appendChild(newField);
+
+ // Behaviour.debug();
+ if(newField) {
+ Behaviour.apply(newField,true);
+ FieldEditor.applyTo('div.FieldEditor');
+ }
+
+ // do we want to make sorting explicit?
+ Sortable.create('Fields_fields', {tag: 'div', handle:'handle'});
+
+ statusMessage('Added new field','good');
+ }
+ }
+ }
+);
+
+function reportError(request){
+ // More complex error for developers
+ statusMessage(request.responseText,'bad');
+
+}
diff --git a/lang/en_US.php b/lang/en_US.php
new file mode 100644
index 0000000..15c5adc
--- /dev/null
+++ b/lang/en_US.php
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/templates/EditableCheckbox.ss b/templates/EditableCheckbox.ss
new file mode 100755
index 0000000..1d1bc0a
--- /dev/null
+++ b/templates/EditableCheckbox.ss
@@ -0,0 +1,26 @@
+
\ No newline at end of file
diff --git a/templates/EditableCheckboxGroupField.ss b/templates/EditableCheckboxGroupField.ss
new file mode 100755
index 0000000..82bec04
--- /dev/null
+++ b/templates/EditableCheckboxGroupField.ss
@@ -0,0 +1,51 @@
+
diff --git a/templates/EditableCheckboxOption.ss b/templates/EditableCheckboxOption.ss
new file mode 100755
index 0000000..70865a9
--- /dev/null
+++ b/templates/EditableCheckboxOption.ss
@@ -0,0 +1,10 @@
+
+
+
+
+ <% if isReadonly %>
+
+ <% else %>
+
+ <% end_if %>
+
\ No newline at end of file
diff --git a/templates/EditableDateField.ss b/templates/EditableDateField.ss
new file mode 100755
index 0000000..c4e6055
--- /dev/null
+++ b/templates/EditableDateField.ss
@@ -0,0 +1,20 @@
+
diff --git a/templates/EditableDropdown.ss b/templates/EditableDropdown.ss
new file mode 100755
index 0000000..70b4f22
--- /dev/null
+++ b/templates/EditableDropdown.ss
@@ -0,0 +1,50 @@
+
diff --git a/templates/EditableDropdownOption.ss b/templates/EditableDropdownOption.ss
new file mode 100755
index 0000000..2bdf47a
--- /dev/null
+++ b/templates/EditableDropdownOption.ss
@@ -0,0 +1,10 @@
+
+
+
+
+ <% if isReadonly %>
+
+ <% else %>
+
+ <% end_if %>
+
\ No newline at end of file
diff --git a/templates/EditableEmailField.ss b/templates/EditableEmailField.ss
new file mode 100755
index 0000000..716c275
--- /dev/null
+++ b/templates/EditableEmailField.ss
@@ -0,0 +1,25 @@
+
\ No newline at end of file
diff --git a/templates/EditableFileField.ss b/templates/EditableFileField.ss
new file mode 100755
index 0000000..aae08dc
--- /dev/null
+++ b/templates/EditableFileField.ss
@@ -0,0 +1,20 @@
+
diff --git a/templates/EditableFormField.ss b/templates/EditableFormField.ss
new file mode 100755
index 0000000..6837b6c
--- /dev/null
+++ b/templates/EditableFormField.ss
@@ -0,0 +1,44 @@
+
\ No newline at end of file
diff --git a/templates/EditableFormFieldOption.ss b/templates/EditableFormFieldOption.ss
new file mode 100755
index 0000000..4ec8971
--- /dev/null
+++ b/templates/EditableFormFieldOption.ss
@@ -0,0 +1,15 @@
+
+ <% if isReadonly %>
+
+ $DefaultSelect
+
+
+
+ <% else %>
+
+ $DefaultSelect
+
+
+
+ <% end_if %>
+
\ No newline at end of file
diff --git a/templates/EditableFormHeading.ss b/templates/EditableFormHeading.ss
new file mode 100755
index 0000000..62319df
--- /dev/null
+++ b/templates/EditableFormHeading.ss
@@ -0,0 +1,17 @@
+
diff --git a/templates/EditableRadioField.ss b/templates/EditableRadioField.ss
new file mode 100755
index 0000000..26c5c55
--- /dev/null
+++ b/templates/EditableRadioField.ss
@@ -0,0 +1,51 @@
+
\ No newline at end of file
diff --git a/templates/EditableRadioOption.ss b/templates/EditableRadioOption.ss
new file mode 100755
index 0000000..70865a9
--- /dev/null
+++ b/templates/EditableRadioOption.ss
@@ -0,0 +1,10 @@
+
+
+
+
+ <% if isReadonly %>
+
+ <% else %>
+
+ <% end_if %>
+
\ No newline at end of file
diff --git a/templates/EditableTextField.ss b/templates/EditableTextField.ss
new file mode 100755
index 0000000..26177db
--- /dev/null
+++ b/templates/EditableTextField.ss
@@ -0,0 +1,20 @@
+
\ No newline at end of file
diff --git a/templates/FieldEditor.ss b/templates/FieldEditor.ss
new file mode 100755
index 0000000..06b365f
--- /dev/null
+++ b/templates/FieldEditor.ss
@@ -0,0 +1,81 @@
+
+
+
+ <% control Fields %>
+ <% if isReadonly %>
+ $ReadonlyEditSegment
+ <% else %>
+ $EditSegment
+ <% end_if %>
+ <% end_control %>
+
+
+
+ <% control FormOptions %>
+ $FieldHolder
+ <% end_control %>
+
\ No newline at end of file
diff --git a/templates/SubmittedFormReportField.ss b/templates/SubmittedFormReportField.ss
new file mode 100644
index 0000000..9bcde60
--- /dev/null
+++ b/templates/SubmittedFormReportField.ss
@@ -0,0 +1,18 @@
+
+ $FilterForm
+
+
\ No newline at end of file
diff --git a/templates/email/SubmittedFormEmail.ss b/templates/email/SubmittedFormEmail.ss
new file mode 100755
index 0000000..40470c3
--- /dev/null
+++ b/templates/email/SubmittedFormEmail.ss
@@ -0,0 +1,21 @@
+
+
+
+
+ $Subject
+
+ <% _t('SUBMITTED','The following data was submitted to your website:') %>
+
+
+ $Body
+
+
+ <% control Fields %>
+
+ $Title
+ $Value
+
+ <% end_control %>
+
+
+