Version: current

       1 <?php
       2
       3 /**
       4  * User Defined Form Page type that lets users build a form in the CMS
       5  * using the FieldEditor Field.
       6  *
       7  * @todo Allow UserDefinedForm instances on Page subclasses (eg via decorator)
       8  *
       9  * @package userforms
      10  */
      11
      12 class UserDefinedForm extends Page {
      13
      14     /**
      15      * @var String Required Identifier
      16      */
      17     static $required_identifier = null;
      18
      19     /**
      20      * @var Array Fields on the user defined form page.
      21      */
      22     static $db = array(
      23         "SubmitButtonText" => "Varchar",
      24         "OnCompleteMessage" => "HTMLText",
      25         'DisableSaveSubmissions' => 'Boolean'
      26     );
      27
      28     /**
      29      * @var Array Default values of variables when this page is created
      30      */
      31     static $defaults = array(
      32         'Content' => '$UserDefinedForm',
      33         'DisableSaveSubmissions' => 0,
      34     );
      35
      36     static $extensions = array(
      37         "Versioned('Stage', 'Live')"
      38     );
      39
      40     /**
      41      * @var Array
      42      */
      43     static $has_many = array(
      44         "Fields" => "EditableFormField",
      45         "Submissions" => "SubmittedForm",
      46         "EmailRecipients" => "UserDefinedForm_EmailRecipient"
      47     );
      48
      49
      50     /**
      51     * Change extended mode
      52     *
      53     */
      54     static function set_extended_mode($mode = false) { return true; }
      55
      56     /**
      57      * @var Флаг использования js-валидации
      58      */
      59     static $use_js_validation = true;
      60
      61     public function populateDefaults() {
      62         parent::populateDefaults();
      63         $this->OnCompleteMessage = _t('UserDefinedForm.OnCompleteMessage');
      64     }
      65
      66     /**
      67      * Setup the CMS Fields for the User Defined Form
      68      *
      69      * @return FieldSet
      70      */
      71     public function getCMSFields() {
      72         $fields = parent::getCMSFields();
      73         Requirements::add_i18n_javascript('userforms/javascript/lang');
      74
      75         // define tabs
      76         $fields->findOrMakeTab('Root.Content.Form', _t('UserDefinedForm.FORM', 'Form'));
      77         $fields->findOrMakeTab('Root.Content.Options', _t('UserDefinedForm.OPTIONS', 'Options'));
      78         $fields->findOrMakeTab('Root.Content.EmailRecipients', _t('UserDefinedForm.EMAILRECIPIENTS', 'Email Recipients'));
      79         $fields->findOrMakeTab('Root.Content.OnComplete', _t('UserDefinedForm.ONCOMPLETE', 'On Complete'));
      80         $fields->findOrMakeTab('Root.Content.Submissions', _t('UserDefinedForm.SUBMISSIONS', 'Submissions'));
      81
      82         // field editor
      83         $fields->addFieldToTab("Root.Content.Form", new FieldEditor("Fields", 'Fields', "", $this ));
      84
      85         // view the submissions
      86         $fields->addFieldToTab("Root.Content.Submissions", new CheckboxField('DisableSaveSubmissions',_t('UserDefinedForm.SAVESUBMISSIONS',"Disable Saving Submissions to Server")));
      87         $fields->addFieldToTab("Root.Content.Submissions", new SubmittedFormReportField( "Reports", _t('UserDefinedForm.RECEIVED', 'Received Submissions'), "", $this ) );
      88
      89         // who do we email on submission
      90         $emailRecipients = new ComplexTableField(
      91             $this,
      92             'EmailRecipients',
      93             'UserDefinedForm_EmailRecipient',
      94             array(
      95                 'EmailAddress' => _t('UserDefinedForm.EMAILADDRESS', 'Email'),
      96                 'EmailSubject' => _t('UserDefinedForm.EMAILSUBJECT', 'Subject'),
      97                 'EmailFrom' => _t('UserDefinedForm.EMAILFROM', 'From')
      98             ),
      99             'getCMSFields_forPopup',
     100             "\"FormID\" = '$this->ID'"
     101         );
     102         $emailRecipients->setAddTitle(_t('UserDefinedForm.AEMAILRECIPIENT', 'A Email Recipient'));
     103
     104         $fields->addFieldToTab("Root.Content.EmailRecipients", $emailRecipients);
     105
     106         // text to show on complete
     107         $onCompleteFieldSet = new FieldSet(
     108             new HtmlEditorField( "OnCompleteMessage", _t('UserDefinedForm.ONCOMPLETELABEL', 'Show on completion'),3,"",_t('UserDefinedForm.ONCOMPLETEMESSAGE', $this->OnCompleteMessage), $this )
     109         );
     110
     111         $fields->addFieldsToTab("Root.Content.OnComplete", $onCompleteFieldSet);
     112         $fields->addFieldsToTab("Root.Content.Options", $this->getFormOptions());
     113
     114         return $fields;
     115     }
     116
     117     function onAfterDelete() {
     118         parent::onAfterDelete();
     119
     120         if ($this->IsDeletedFromStage && !$this->ExistsOnLive) {
     121             if ($this->Submissions()->Count()) {
     122                 foreach($this->Submissions() as $submission) {
     123                     $submission->delete();
     124                 }
     125             }
     126             if ($this->EmailRecipients()->Count()) {
     127                 foreach($this->EmailRecipients() as $emailRecipient) {
     128                     $emailRecipient->delete();
     129                 }
     130             }
     131             if ($this->Fields()->Count()) {
     132                 foreach($this->Fields() as $field) {
     133                     $field->deleteFromStage('Stage');
     134                     $field->deleteFromStage('Live');
     135                 }
     136             }
     137         }
     138     }
     139
     140     /**
     141      * Publishing Versioning support.
     142      *
     143      * When publishing copy the editable form fields to the live database
     144      * Not going to version emails and submissions as they are likely to
     145      * persist over multiple versions
     146      *
     147      * @return void
     148      */
     149     public function doPublish() {
     150         // remove fields on the live table which could have been orphaned.
     151         $live = Versioned::get_by_stage("EditableFormField", "Live", "\"EditableFormField\".\"ParentID\" = $this->ID");
     152
     153         if($live) {
     154             foreach($live as $field) {
     155                 $field->doDeleteFromStage('Live');
     156             }
     157         }
     158
     159         // publish the draft pages
     160         if($this->Fields()) {
     161             foreach($this->Fields() as $field) {
     162                 $field->doPublish('Stage', 'Live');
     163             }
     164         }
     165
     166         parent::doPublish();
     167     }
     168
     169     /**
     170      * Unpublishing Versioning support
     171      *
     172      * When unpublishing the page it has to remove all the fields from
     173      * the live database table
     174      *
     175      * @return void
     176      */
     177     public function doUnpublish() {
     178         if($this->Fields()) {
     179             foreach($this->Fields() as $field) {
     180                 $field->doDeleteFromStage('Live');
     181             }
     182         }
     183
     184         parent::doUnpublish();
     185     }
     186
     187     /**
     188      * Roll back a form to a previous version.
     189      *
     190      * @param String|int Version to roll back to
     191      */
     192     public function doRollbackTo($version) {
     193         parent::doRollbackTo($version);
     194
     195         /*
     196             Not implemented yet
     197
     198         // get the older version
     199         $reverted = Versioned::get_version($this->ClassName, $this->ID, $version);
     200
     201         if($reverted) {
     202
     203             // using the lastedited date of the reverted object we can work out which
     204             // form fields to revert back to
     205             if($this->Fields()) {
     206                 foreach($this->Fields() as $field) {
     207                     // query to see when the version of the page was pumped
     208                     $editedDate = DB::query("
     209                         SELECT LastEdited
     210                         FROM \"SiteTree_versions\"
     211                         WHERE \"RecordID\" = '$this->ID' AND \"Version\" = $version
     212                     ")->value();
     213
     214
     215                     // find a the latest version which has been edited
     216                     $versionToGet = DB::query("
     217                         SELECT *
     218                         FROM \"EditableFormField_versions\"
     219                         WHERE \"RecordID\" = '$field->ID' AND \"LastEdited\" <= '$editedDate'
     220                         ORDER BY Version DESC
     221                         LIMIT 1
     222                     ")->record();
     223
     224                     if($versionToGet) {
     225                         Debug::show('publishing field'. $field->Name);
     226                         Debug::show($versionToGet);
     227                         $field->publish($versionToGet, "Stage", true);
     228                         $field->writeWithoutVersion();
     229                     }
     230                     else {
     231                         Debug::show('deleting field'. $field->Name);
     232                         $this->Fields()->remove($field);
     233
     234                         $field->delete();
     235                         $field->destroy();
     236                     }
     237                 }
     238             }
     239
     240             // @todo Emails
     241         }
     242         */
     243     }
     244
     245     /**
     246      * Revert the draft site to the current live site
     247      *
     248      * @return void
     249      */
     250     public function doRevertToLive() {
     251         if($this->Fields()) {
     252             foreach($this->Fields() as $field) {
     253                 $field->publish("Live", "Stage", false);
     254                 $field->writeWithoutVersion();
     255             }
     256         }
     257
     258         parent::doRevertToLive();
     259     }
     260
     261     /**
     262      * Duplicate this UserDefinedForm page, and its form fields.
     263      * Submissions, on the other hand, won't be duplicated.
     264      *
     265      * @return Page
     266      */
     267     public function duplicate($doWrite = true) {
     268         $page = parent::duplicate($doWrite);
     269
     270         // the form fields
     271         if($this->Fields()) {
     272             foreach($this->Fields() as $field) {
     273                 $newField = $field->duplicate();
     274                 $newField->ParentID = $page->ID;
     275                 $newField->write();
     276             }
     277         }
     278
     279         // the emails
     280         if($this->EmailRecipients()) {
     281             foreach($this->EmailRecipients() as $email) {
     282                 $newEmail = $email->duplicate();
     283                 $newEmail->FormID = $page->ID;
     284                 $newEmail->write();
     285             }
     286         }
     287
     288         return $page;
     289     }
     290
     291     /**
     292      * Custom options for the form. You can extend the built in options by
     293      * using {@link updateFormOptions()}
     294      *
     295      * @return FieldSet
     296      */
     297     public function getFormOptions() {
     298         $submit = ($this->SubmitButtonText) ? $this->SubmitButtonText : _t('UserDefinedForm.SUBMITBUTTON', 'Submit');
     299
     300         $options = new FieldSet(
     301             new TextField("SubmitButtonText", _t('UserDefinedForm.TEXTONSUBMIT', 'Text on submit button:'), $submit)
     302         );
     303
     304         $this->extend('updateFormOptions', $options);
     305
     306         return $options;
     307     }
     308
     309     /**
     310      * Return if this form has been modified on the stage site and not published.
     311      * this is used on the workflow module and for a couple highlighting things
     312      *
     313      * @todo make this a bit smarter - the issue with userforms is that it uses several
     314      *      relationships to form fields which has a undefined amount of options so
     315      *      for now just say its always modified
     316      */
     317
     318     public function getIsModifiedOnStage() {
     319         if ($this->RelationshipAggregate('Fields')->Max('LastEdited') > $this->LastEdited) return true;
     320         if ($this->RelationshipAggregate('EmailRecipients')->Max('LastEdited') > $this->LastEdited) return true;
     321         return parent::getIsModifiedOnStage();
     322     }
     323
     324 }
     325
     326 /**
     327  * Controller for the {@link UserDefinedForm} page type.
     328  *
     329  * @package userform
     330  * @subpackage pagetypes
     331  */
     332
     333 class UserDefinedForm_Controller extends Page_Controller {
     334
     335     // суффикс для ID-формы, чтоб отличать одинаковые формы на одной странице
     336     protected $suffix;
     337
     338     /**
     339      * Load all the custom jquery needed to run the custom
     340      * validation
     341      */
     342     public function init() {
     343         parent::init();
     344
     345         $this->formInit();
     346     }
     347
     348     /**
     349      * Выносим подгрузку скриптов в функцию, чтоб ее можно было вызвать отдельно (напр. в виджете)
     350      *
     351      */
     352     function formInit() {
     353         // block prototype validation
     354         Validator::set_javascript_validation_handler('none');
     355
     356         // load the jquery
     357         if (UserDefinedForm::$use_js_validation) {
     358             Requirements::javascript(JQUERY_PATH);
     359             Requirements::javascript('site/js/vendor/jquery.validate.js');
     360             // !!!! узнать у Саши и заменить - взял новые с https://github.com/jquery-validation/jquery-validation/tree/master/src/localization
     361             Requirements::javascript('userforms/thirdparty/jquery-validate/localization/messages_'.i18n::get_lang_from_locale(i18n::get_locale()).'.js');
     362         }
     363     }
     364
     365     /**
     366      * Using $UserDefinedForm in the Content area of the page shows
     367      * where the form should be rendered into. If it does not exist
     368      * then default back to $Form
     369      *
     370      * @return Array
     371      */
     372     public function index() {
     373         $form = $this->Form();
     374         $customScripts = '';
     375         if ($this->isAjax()) {
     376             $this->suffix = 'ajax';
     377             $form->setHtmlID($this->generateFormID());
     378             $form->loadDataFrom(array('HtmlIDSuffix' => $this->suffix));
     379             $form->addExtraClass('form__ajax');
     380             $customScripts = $this->generateConditionalJavascript(true);
     381         }
     382         if($this->Content && $form) {
     383             $hasLocation = stristr($this->Content, '$UserDefinedForm');
     384             if($hasLocation) {
     385                 $content = str_ireplace('$UserDefinedForm', $form->forTemplate(), $this->Content);
     386                 return array(
     387                     'Content' => DBField::create('HTMLText', $content),
     388                     'Form' => "",
     389                     'customScripts' => $customScripts,
     390                 );
     391             }
     392         }
     393
     394         return array(
     395             'Content' => DBField::create('HTMLText', $this->Content),
     396             'Form' => $form,
     397             'customScripts' => $customScripts,
     398         );
     399     }
     400
     401     /**
     402      * Get the form for the page. Form can be modified by calling {@link updateForm()}
     403      * on a UserDefinedForm extension
     404      *
     405      * @return Form|false
     406      */
     407     function Form() {
     408         $fields = $this->getFormFields();
     409         if(!$fields) return false;
     410
     411         if (isset($_REQUEST['HtmlIDSuffix']) && trim($_REQUEST['HtmlIDSuffix'])) {
     412             $this->suffix = trim($_REQUEST['HtmlIDSuffix']);
     413         }
     414         $actions = $this->getFormActions();
     415
     416         // get the required fields including the validation
     417         $required = $this->getRequiredFields();
     418
     419         // generate the conditional logic
     420         $this->generateConditionalJavascript();
     421
     422         $form = new Form($this, "Form", $fields, $actions, $required);
     423
     424         $form->setHtmlID($this->generateFormID());
     425         $form->addExtraClass('UserForm');
     426
     427         // дополнительно формируем ошибки после указания правильного HtmlID
     428         $form->setupFormErrors();
     429
     430         $data = Session::get("FormInfo.{$form->FormName()}.data");
     431
     432         if(is_array($data)) $form->loadDataFrom($data);
     433
     434         $this->extend('updateForm', $form);
     435
     436         return $form;
     437     }
     438
     439     function generateFormID() {
     440 //      return 'Form_Form';
     441         return 'UserForm'.$this->ID.($this->suffix ? "_{$this->suffix}" : "");
     442     }
     443
     444     /**
     445      * Get the form fields for the form on this page. Can modify this FieldSet
     446      * by using {@link updateFormFields()} on an {@link Extension} subclass which
     447      * is applied to this controller
     448      *
     449      * @return FieldSet
     450      */
     451     function getFormFields() {
     452         $fields = new FieldSet();
     453
     454         if($this->Fields()) {
     455             foreach($this->Fields() as $editableField) {
     456                 // get the raw form field from the editable version
     457                 $field = $editableField->getFormField();
     458                 if(!$field) break;
     459
     460                 // set the error / formatting messages
     461                 $field->setCustomValidationMessage($editableField->getErrorMessage());
     462
     463                 // set the right title on this field
     464                 if($right = $editableField->getSetting('RightTitle')) {
     465                     $field->setRightTitle($right);
     466                 }
     467
     468                 // if this field is required add some
     469                 if($editableField->Required) {
     470                     $field->addExtraClass('requiredField');
     471
     472                     if($identifier = UserDefinedForm::$required_identifier) {
     473
     474                         $title = $field->Title() ." <span class='required-identifier'>". $identifier . "</span>";
     475                         $field->setTitle($title);
     476                     }
     477                 }
     478                 // if this field has an extra class
     479                 if($editableField->getSetting('ExtraClass')) {
     480                     $extraClasses = explode(' ', $editableField->getSetting('ExtraClass'));
     481                     foreach($extraClasses as $extraClass) {
     482                         if (trim($extraClass)) {
     483                             $field->addExtraClass(Convert::raw2att(trim($extraClass)));
     484                         }
     485                     }
     486                 }
     487
     488                 //добавляем placeholder
     489                 if($editableField->getSetting('Placeholder')) {
     490                     $field->addExtraAttribute('placeholder', Convert::raw2att($editableField->getSetting('Placeholder')));
     491                 }
     492
     493                 // set the values passed by the url to the field
     494                 $request = $this->getRequest();
     495                 if($request && $var = $request->getVar($field->name)) {
     496                     $field->value = Convert::raw2att($var);
     497                 }
     498
     499                 $fields->push($field);
     500             }
     501         }
     502         $fields->push(new HiddenField('HtmlIDSuffix'));
     503         $this->extend('updateFormFields', $fields);
     504
     505         return $fields;
     506     }
     507
     508     /**
     509      * Generate the form actions for the UserDefinedForm. You
     510      * can manipulate these by using {@link updateFormActions()} on
     511      * a decorator.
     512      *
     513      * @todo Make form actions editable via their own field editor.
     514      *
     515      * @return FieldSet
     516      */
     517     function getFormActions() {
     518         $submitText = ($this->SubmitButtonText) ? $this->SubmitButtonText : _t('UserDefinedForm.SUBMITBUTTON', 'Submit');
     519
     520         $actions = new FieldSet(
     521             new FormAction("process", $submitText)
     522         );
     523
     524         $this->extend('updateFormActions', $actions);
     525
     526         return $actions;
     527     }
     528
     529     /**
     530      * Get the required form fields for this form. Includes building the jQuery
     531      * validate structure
     532      *
     533      * @return RequiredFields
     534      */
     535     function getRequiredFields() {
     536         $required = new RequiredFields();
     537
     538         $rules = array();
     539         $validation = array();
     540         $messages = array();
     541
     542         if($this->Fields()) {
     543             foreach($this->Fields() as $field) {
     544                 $messages[$field->Name] = $field->getErrorMessage()->HTML();
     545
     546                 if ($field->CustomRules()->Count() == 0) {
     547                     $rules[$field->Name] = $field->getValidation();
     548                     if ($field->Required) {
     549                         $required->addRequiredField($field->Name);
     550                     }
     551                 }
     552             }
     553         }
     554
     555         if (UserDefinedForm::$use_js_validation) {
     556             // Set the Form Name
     557             $rules = $this->array2json($rules);
     558             $messages = $this->array2json($messages);
     559
     560             // set the custom script for this form
     561             Requirements::customScript(<<<JS
     562 (function($) {
     563     $(document).ready(function() {
     564         //настраиваем валидацию
     565         var validateOptions = {
     566             ignore: [':hidden'],
     567             errorElement: "span",
     568             validClass: "is-valid",
     569             errorClass: "required",
     570             errorPlacement: function(error, element) {
     571                 if (element.is(":radio")) {
     572                     error.insertAfter(element.closest("ul"));
     573                 } else if (element.is(":checkbox")) {
     574                     console.log(element)
     575                     var wrapper = element.closest('.middlecolumn');
     576                     if(wrapper) {
     577                         error.insertAfter(wrapper);
     578                     } else {
     579                         error.insertAfter(element);
     580                     };
     581                 } else {
     582                     error.insertAfter(element);
     583                 }
     584             },
     585             messages:
     586                 $messages
     587             ,
     588             rules:
     589                 $rules
     590         };
     591         $("#{$this->generateFormID()}").validate(validateOptions);
     592     });
     593 })(jQuery);
     594 JS
     595                 , 'Validation' . $this->generateFormID());
     596         }
     597
     598         $this->extend('updateRequiredFields', $required);
     599
     600         return $required;
     601     }
     602
     603     /**
     604      * Generate the javascript for the conditional field show / hiding logic.
     605      * Allows complex rules to be created
     606      * @return void
     607      */
     608     function generateConditionalJavascript($asText=false) {
     609         $default = "";
     610         $rules = "";
     611
     612         if($this->Fields()) {
     613             foreach($this->Fields() as $field) {
     614                 $fieldId = $field->Name;
     615
     616                 //if($field->ClassName == 'EditableFormHeading') {
     617                     $fieldId = $this->generateFormID().'_'.$field->Name;
     618                 //}
     619
     620                 // Is this Field Show by Default
     621                 if(!$field->getShowOnLoad()) {
     622                     $default .= "$(\"#" . $fieldId . "\").hide();\n";
     623                 }
     624
     625                 // Check for field dependencies / default
     626                 if($field->Dependencies()) {
     627                     foreach($field->Dependencies() as $dependency) {
     628                         if(is_array($dependency) && isset($dependency['ConditionField']) && $dependency['ConditionField'] != "") {
     629                             // get the field which is effected
     630                             $formName = Convert::raw2sql($dependency['ConditionField']);
     631                             $formFieldWatch = DataObject::get_one("EditableFormField", "\"Name\" = '$formName'");
     632                             $fieldWatchId = $this->generateFormID().'_'.$formFieldWatch->Name;
     633
     634                             if(!$formFieldWatch) break;
     635
     636                             // watch out for multiselect options - radios and check boxes
     637                             if(is_a($formFieldWatch, 'EditableDropdown')) {
     638                                 $fieldToWatch = "$(\"select#{$fieldWatchId}\")";
     639                             }
     640
     641                             // watch out for checkboxs as the inputs don't have values but are 'checked
     642                             else if(is_a($formFieldWatch, 'EditableCheckboxGroupField')) {
     643                                 $fieldToWatch = "$(\"input#{$fieldWatchId}[".$dependency['Value']."]']\")";
     644                             }
     645                             else {
     646                                 $fieldToWatch = "$(\"input#{$fieldWatchId}\")";
     647                             }
     648
     649                             // show or hide?
     650                             $view = (isset($dependency['Display']) && $dependency['Display'] == "Hide") ? "hide" : "show";
     651                             $opposite = ($view == "show") ? "hide" : "show";
     652
     653                             // what action do we need to keep track of. Something nicer here maybe?
     654                             // @todo encapulsation
     655                             $action = "change";
     656
     657                             if($formFieldWatch->ClassName == "EditableTextField" || $formFieldWatch->ClassName == "EditableDateField") {
     658                                 $action = "keyup";
     659                             }
     660
     661                             // is this field a special option field
     662                             $checkboxField = false;
     663                             if(in_array($formFieldWatch->ClassName, array('EditableCheckboxGroupField', 'EditableCheckbox'))) {
     664                                 $action = "click";
     665                                 $checkboxField = true;
     666                             }
     667
     668                             // and what should we evaluate
     669                             switch($dependency['ConditionOption']) {
     670                                 case 'IsNotBlank':
     671                                     $expression = ($checkboxField) ? '$(this).attr("checked")' :'$(this).val() != ""';
     672
     673                                     break;
     674                                 case 'IsBlank':
     675                                     $expression = ($checkboxField) ? '!($(this).attr("checked"))' : '$(this).val() == ""';
     676
     677                                     break;
     678                                 case 'HasValue':
     679                                     $expression = ($checkboxField) ? '$(this).attr("checked")' : '$(this).val() == "'. $dependency['Value'] .'"';
     680
     681                                     break;
     682                                 case 'ValueLessThan':
     683                                     $expression = '$(this).val() < parseFloat("'. $dependency['Value'] .'")';
     684
     685                                     break;
     686                                 case 'ValueLessThanEqual':
     687                                     $expression = '$(this).val() <= parseFloat("'. $dependency['Value'] .'")';
     688
     689                                     break;
     690                                 case 'ValueGreaterThan':
     691                                     $expression = '$(this).val() > parseFloat("'. $dependency['Value'] .'")';
     692
     693                                     break;
     694                                 case 'ValueGreaterThanEqual':
     695                                     $expression = '$(this).val() >= parseFloat("'. $dependency['Value'] .'")';
     696
     697                                     break;
     698                                 default:
     699                                     $expression = ($checkboxField) ? '!($(this).attr("checked"))' : '$(this).val() != "'. $dependency['Value'] .'"';
     700
     701                                     break;
     702                             }
     703                             // put it all together
     704                             $rules .= $fieldToWatch.".$action(function() {
     705                                 if(". $expression ." ) {
     706                                     $(\"#". $fieldId ."_wrapper\").".$view."();
     707                                 }
     708                                 else {
     709                                     $(\"#". $fieldId ."_wrapper\").".$opposite."();
     710                                 }
     711                             });";
     712                         }
     713                     }
     714                 }
     715             }
     716         }
     717
     718         if ($rules || $default) {
     719             $script =
     720 <<<JS
     721             (function($) {
     722                 $(document).ready(function() {
     723                     $rules
     724
     725                     $default
     726                 })
     727             })(jQuery);
     728 JS;
     729             if ($asText) {
     730                 return $script;
     731             } else {
     732                 Requirements::customScript($script, 'Conditional' . $this->generateFormID());
     733             }
     734
     735
     736         }
     737
     738     }
     739
     740     /**
     741      * Convert a PHP array to a JSON string. We cannot use {@link Convert::array2json}
     742      * as it escapes our values with "" which appears to break the validate plugin
     743      *
     744      * @param Array array to convert
     745      * @return JSON
     746      */
     747     function array2json($array) {
     748         foreach($array as $key => $value)
     749             if(is_array( $value )) {
     750                 $result[] = "$key:" . $this->array2json($value);
     751             } else {
     752                 $value = (is_bool($value)) ? $value : "\"$value\"";
     753                 $result[] = "$key:$value";
     754             }
     755         return (isset($result)) ? "{\n".implode( ', ', $result ) ."\n}\n": '{}';
     756     }
     757
     758     /**
     759      * Process the form that is submitted through the site
     760      *
     761      * @param Array Data
     762      * @param Form Form
     763      * @return Redirection
     764      */
     765     function process($data, $form) {
     766         if (isset($data['HtmlIDSuffix']) && trim($data['HtmlIDSuffix'])) {
     767             $this->suffix = trim($data['HtmlIDSuffix']);
     768             $form->setHtmlID($this->generateFormID());
     769         }
     770         Session::set("FormInfo.{$form->FormName()}.data",$data);
     771         Session::clear("FormInfo.{$form->FormName()}.errors");
     772
     773         foreach($this->Fields() as $field) {
     774             $messages[$field->Name] = $field->getErrorMessage()->HTML();
     775             if($field->Required && $field->CustomRules()->Count() == 0 && $field->ClassName != 'EditableSpamProtectionField') {
     776                 // EditableSpamProtectionField - можно проверить всего 1 раз и это делает $this->validator
     777                 if( !isset($data[$field->Name]) ||
     778                     !$data[$field->Name] ||
     779                     !$field->getFormField()->validate($this->validator)
     780                 ){
     781                     $form->addErrorMessage($field->Name,$field->getErrorMessage()->HTML(),'bad');
     782                 }
     783             }
     784         }
     785
     786         if(Session::get("FormInfo.{$form->FormName()}.errors")){
     787             if (Director::is_ajax()) {
     788                 return $this->index();
     789             }
     790             Director::redirectBack();
     791             return;
     792         }
     793
     794         $submittedForm = SS_Object::create('SubmittedForm');
     795         $submittedForm->SubmittedByID = ($id = Member::currentUserID()) ? $id : 0;
     796         $submittedForm->ParentID = $this->ID;
     797
     798         // if saving is not disabled save now to generate the ID
     799         if(!$this->DisableSaveSubmissions) $submittedForm->write();
     800
     801         $values = array();
     802         $attachments = array();
     803
     804         $submittedFields = new DataObjectSet();
     805
     806         foreach($this->Fields() as $field) {
     807
     808             if(!$field->showInReports()) continue;
     809
     810             $submittedField = $field->getSubmittedFormField();
     811             $submittedField->ParentID = $submittedForm->ID;
     812             $submittedField->Name = $field->Name;
     813             $submittedField->Title = $field->getField('Title');
     814
     815             // save the value from the data
     816             if($field->hasMethod('getValueFromData')) {
     817                 $submittedField->Value = $field->getValueFromData($data);
     818             }
     819             else {
     820                 if(isset($data[$field->Name])) $submittedField->Value = $data[$field->Name];
     821             }
     822
     823             if(!empty($data[$field->Name])){
     824                 if(in_array("EditableFileField", $field->getClassAncestry())) {
     825                     if(isset($_FILES[$field->Name])) {
     826
     827                         // create the file from post data
     828                         $upload = new Upload();
     829                         $file = new File();
     830                         $file->ShowInSearch = 0;
     831
     832                         $upload->loadIntoFile($_FILES[$field->Name], $file);
     833
     834                         // write file to form field
     835                         $submittedField->UploadedFileID = $file->ID;
     836
     837                         // attach a file only if lower than 1MB
     838                         if($file->getAbsoluteSize() < 1024*1024*1){
     839                             $attachments[] = $file;
     840                         }
     841                     }
     842                 }
     843             }
     844
     845             if(!$this->DisableSaveSubmissions) $submittedField->write();
     846
     847             $submittedFields->push($submittedField);
     848         }
     849
     850         $emailData = array(
     851             "Sender" => Member::currentUser(),
     852             "Fields" => $submittedFields,
     853             'SenderIP' => $_SERVER{'REMOTE_ADDR'},
     854         );
     855
     856         $this->extend('onBeforeSend', $submittedForm, $emailData);
     857
     858         // email users on submit.
     859         if($this->EmailRecipients()) {
     860
     861             $email = new UserDefinedForm_SubmittedFormEmail($submittedFields);
     862             $email->populateTemplate($emailData);
     863
     864             if($attachments){
     865                 foreach($attachments as $file){
     866                     if($file->ID != 0) {
     867                         $email->attachFile($file->Filename,$file->Filename, HTTP::getMimeType($file->Filename));
     868                     }
     869                 }
     870             }
     871
     872             foreach($this->EmailRecipients() as $recipient) {
     873                 $email->populateTemplate($recipient);
     874                 $email->populateTemplate($emailData);
     875                 $email->setFrom($recipient->EmailFrom);
     876                 $email->setBody($recipient->EmailBody);
     877                 $email->setSubject($recipient->EmailSubject);
     878                 $email->setTo($recipient->EmailAddress);
     879
     880                 // check to see if they are a dynamic sender. eg based on a email field a user selected
     881                 if($recipient->SendEmailFromField()) {
     882                     $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailFromField()->Name);
     883                     if($submittedFormField) {
     884                         $email->setFrom($submittedFormField->Value);
     885                     }
     886                 }
     887                 // check to see if they are a dynamic reciever eg based on a dropdown field a user selected
     888                 if($recipient->SendEmailToField()) {
     889                     $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailToField()->Name);
     890
     891                     if($submittedFormField) {
     892                         $email->setTo($submittedFormField->Value);
     893                     }
     894                 }
     895
     896                 if (!trim($email->To())) {
     897                     continue; // получатель не указан
     898                 }
     899                 if (!$plainBody = $email->renderWith('SubmittedFormEmail_plain')) {
     900                     $plainBody = strip_tags($recipient->EmailBody) . "\n\n";
     901                     if (isset($emailData['Fields']) && !$recipient->HideFormData) {
     902                         foreach($emailData['Fields'] as $Field) {
     903                             $plainBody .= ($Field->Title ? $Field->Title : $Field->Name) .': '. $Field->Value ."\n";
     904                         }
     905                     }
     906                 }
     907
     908                 $email->setPlainBody($plainBody);
     909
     910                 if($recipient->SendPlain) {
     911                     $email->sendPlain();
     912                 }
     913                 else {
     914                     $email->send();
     915                 }
     916             }
     917         }
     918
     919         Session::clear("FormInfo.{$form->FormName()}.errors");
     920         Session::clear("FormInfo.{$form->FormName()}.data");
     921
     922         $this->extend('onAfterProccess', $submittedForm, $emailData);
     923
     924         if (Director::is_ajax()) return $this->OnCompleteMessage;
     925         /*
     926         if ($this->isAjax()) {
     927             if ($request = $this->getRequest()) {
     928                 if ($acceptType = $request->getHeader('Accept')) {
     929                     if(strpos($acceptType, 'application/json') !== FALSE) {
     930                         return json_encode(array(array(
     931                             'type' => 'good',
     932                             'message' => $this->OnCompleteMessage
     933                         )));
     934                     }
     935                 }
     936             }
     937             return $this->OnCompleteMessage;
     938         }
     939         */
     940         $referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
     941         return Director::redirect($this->Link() . 'finished' . $referrer);
     942     }
     943
     944     /**
     945      * This action handles rendering the "finished" message,
     946      * which is customisable by editing the ReceivedFormSubmission.ss
     947      * template.
     948      *
     949      * @return ViewableData
     950      */
     951     function finished() {
     952         $referrer = isset($_GET['referrer']) ? urldecode($_GET['referrer']) : null;
     953
     954         return $this->customise(array(
     955             'Content' => $this->customise(
     956                 array(
     957                     'Link' => $referrer
     958                 ))->renderWith('ReceivedFormSubmission'),
     959             'Form' => '',
     960         ));
     961     }
     962 }
     963
     964 /**
     965  * A Form can have multiply members / emails to email the submission
     966  * to and custom subjects
     967  *
     968  * @package userforms
     969  */
     970 class UserDefinedForm_EmailRecipient extends DataObject {
     971
     972     static $db = array(
     973         'EmailAddress' => 'Varchar(200)',
     974         'EmailSubject' => 'Varchar(200)',
     975         'EmailFrom' => 'Varchar(200)',
     976         'EmailBody' => 'Text',
     977         'SendPlain' => 'Boolean',
     978         'HideFormData' => 'Boolean'
     979     );
     980
     981     static $has_one = array(
     982         'Form' => 'UserDefinedForm',
     983         'SendEmailFromField' => 'EditableFormField',
     984         'SendEmailToField' => 'EditableFormField'
     985     );
     986
     987     /**
     988      * Return the fields to edit this email.
     989      * @return FieldSet
     990      */
     991     public function getCMSFields_forPopup() {
     992
     993         $fields = new FieldSet(
     994             new TextField('EmailSubject', _t('UserDefinedForm.EMAILSUBJECT', 'Email Subject')),
     995             new TextField('EmailFrom', _t('UserDefinedForm.FROMADDRESS','Send Email From')),
     996             new TextField('EmailAddress', _t('UserDefinedForm.SENDEMAILTO','Send Email To')),
     997             new CheckboxField('HideFormData', _t('UserDefinedForm.HIDEFORMDATA', 'Hide Form Data from Email')),
     998             new CheckboxField('SendPlain', _t('UserDefinedForm.SENDPLAIN', 'Send Email as Plain Text (HTML will be stripped)')),
     999             new TextareaField('EmailBody', _t('UserDefinedForm.EMAILBODY','Body'))
    1000         );
    1001
    1002         if($this->Form()) {
    1003             $validEmailFields = DataObject::get("EditableEmailField", "\"ParentID\" = '" . (int)$this->FormID . "'");
    1004             $multiOptionFields = DataObject::get("EditableMultipleOptionField", "\"ParentID\" = '" . (int)$this->FormID . "'");
    1005
    1006             // if they have email fields then we could send from it
    1007             if($validEmailFields) {
    1008                 $fields->insertAfter(new DropdownField('SendEmailFromFieldID', _t('UserDefinedForm.ORSELECTAFIELDTOUSEASFROM', '.. or Select a Form Field to use as the From Address'), $validEmailFields->toDropdownMap('ID', 'Title'), '', null,""), 'EmailFrom');
    1009             }
    1010
    1011             // if they have multiple options
    1012             if($multiOptionFields || $validEmailFields) {
    1013
    1014                 if($multiOptionFields && $validEmailFields) {
    1015                     $multiOptionFields->merge($validEmailFields);
    1016
    1017                 }
    1018                 elseif(!$multiOptionFields) {
    1019                     $multiOptionFields = $validEmailFields;
    1020                 }
    1021
    1022                 $multiOptionFields = $multiOptionFields->toDropdownMap('ID', 'Title');
    1023                 $fields->insertAfter(new DropdownField('SendEmailToFieldID', _t('UserDefinedForm.ORSELECTAFIELDTOUSEASTO', '.. or Select a Field to use as the To Address'), $multiOptionFields, '', null, ""), 'EmailAddress');
    1024             }
    1025         }
    1026
    1027         return $fields;
    1028     }
    1029
    1030     function canEdit($member = NULL) {
    1031         return $this->Form()->canEdit();
    1032     }
    1033
    1034     function canDelete($member = NULL) {
    1035         return $this->Form()->canEdit();
    1036     }
    1037 }
    1038
    1039 /**
    1040  * Email that gets sent to the people listed in the Email Recipients
    1041  * when a submission is made
    1042  *
    1043  * @package userforms
    1044  */
    1045
    1046 class UserDefinedForm_SubmittedFormEmail extends Email {
    1047     protected $ss_template = "SubmittedFormEmail";
    1048     protected $data;
    1049
    1050     function __construct() {
    1051         parent::__construct();
    1052     }
    1053 }
    1054

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -