Passed
Push — master ( ebdfbb...20570e )
by
unknown
02:32
created

code/Control/UserDefinedFormController.php (1 issue)

Severity
1
<?php
2
3
namespace SilverStripe\UserForms\Control;
4
5
use PageController;
6
use SilverStripe\Assets\File;
7
use SilverStripe\Assets\Upload;
8
use SilverStripe\Control\Controller;
9
use SilverStripe\Control\Email\Email;
10
use SilverStripe\Control\HTTPResponse;
11
use SilverStripe\Control\HTTPRequest;
12
use SilverStripe\Core\Manifest\ModuleLoader;
13
use SilverStripe\Forms\Form;
14
use SilverStripe\i18n\i18n;
15
use SilverStripe\ORM\ArrayList;
16
use SilverStripe\ORM\FieldType\DBField;
17
use SilverStripe\ORM\ValidationException;
18
use SilverStripe\Security\Security;
19
use SilverStripe\UserForms\Form\UserForm;
20
use SilverStripe\UserForms\Model\EditableFormField\EditableFileField;
21
use SilverStripe\UserForms\Model\Submission\SubmittedForm;
22
use SilverStripe\View\ArrayData;
23
use SilverStripe\View\Requirements;
24
use SilverStripe\View\SSViewer;
25
26
/**
27
 * Controller for the {@link UserDefinedForm} page type.
28
 *
29
 * @package userforms
30
 */
31
class UserDefinedFormController extends PageController
32
{
33
    private static $finished_anchor = '#uff';
34
35
    private static $allowed_actions = [
36
        'index',
37
        'ping',
38
        'Form',
39
        'finished'
40
    ];
41
42
    protected function init()
43
    {
44
        parent::init();
45
46
        $page = $this->data();
47
48
        // load the css
49
        if (!$page->config()->get('block_default_userforms_css')) {
50
            Requirements::css('silverstripe/userforms:client/dist/styles/userforms.css');
51
        }
52
53
        // load the jquery
54
        if (!$page->config()->get('block_default_userforms_js')) {
55
            Requirements::javascript('//code.jquery.com/jquery-3.3.1.min.js');
56
            Requirements::javascript(
57
                'silverstripe/userforms:client/thirdparty/jquery-validate/jquery.validate.min.js'
58
            );
59
            Requirements::javascript('silverstripe/admin:client/dist/js/i18n.js');
60
            Requirements::add_i18n_javascript('silverstripe/userforms:client/lang');
61
            Requirements::javascript('silverstripe/userforms:client/dist/js/userforms.js');
62
63
            $this->addUserFormsValidatei18n();
64
65
            // Bind a confirmation message when navigating away from a partially completed form.
66
            if ($page::config()->get('enable_are_you_sure')) {
67
                Requirements::javascript(
68
                    'silverstripe/userforms:client/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js'
69
                );
70
            }
71
        }
72
    }
73
74
    /**
75
     * Add the necessary jQuery validate i18n translation files, either by locale or by langauge,
76
     * e.g. 'en_NZ' or 'en'. This adds "methods_abc.min.js" as well as "messages_abc.min.js" from the
77
     * jQuery validate thirdparty library.
78
     */
79
    protected function addUserFormsValidatei18n()
80
    {
81
        $module = ModuleLoader::getModule('silverstripe/userforms');
82
83
        $candidates = [
84
            i18n::getData()->langFromLocale(i18n::config()->get('default_locale')),
85
            i18n::config()->get('default_locale'),
86
            i18n::getData()->langFromLocale(i18n::get_locale()),
87
            i18n::get_locale(),
88
        ];
89
90
        foreach ($candidates as $candidate) {
91
            foreach (['messages', 'methods'] as $candidateType) {
92
                $localisationCandidate = "client/thirdparty/jquery-validate/localization/{$candidateType}_{$candidate}.min.js";
93
94
                $resource = $module->getResource($localisationCandidate);
95
                if ($resource->exists()) {
96
                    Requirements::javascript($resource->getRelativePath());
97
                }
98
            }
99
        }
100
    }
101
102
    /**
103
     * Using $UserDefinedForm in the Content area of the page shows
104
     * where the form should be rendered into. If it does not exist
105
     * then default back to $Form.
106
     *
107
     * @return array
108
     */
109
    public function index(HTTPRequest $request = null)
110
    {
111
        if ($this->Content && $form = $this->Form()) {
112
            $hasLocation = stristr($this->Content, '$UserDefinedForm');
113
            if ($hasLocation) {
114
                /** @see Requirements_Backend::escapeReplacement */
115
                $formEscapedForRegex = addcslashes($form->forTemplate(), '\\$');
116
                $content = preg_replace(
117
                    '/(<p[^>]*>)?\\$UserDefinedForm(<\\/p>)?/i',
118
                    $formEscapedForRegex,
119
                    $this->Content
120
                );
121
                return [
122
                    'Content' => DBField::create_field('HTMLText', $content),
123
                    'Form' => ''
124
                ];
125
            }
126
        }
127
128
        return [
129
            'Content' => DBField::create_field('HTMLText', $this->Content),
130
            'Form' => $this->Form()
131
        ];
132
    }
133
134
    /**
135
     * Keep the session alive for the user.
136
     *
137
     * @return int
138
     */
139
    public function ping()
140
    {
141
        return 1;
142
    }
143
144
    /**
145
     * Get the form for the page. Form can be modified by calling {@link updateForm()}
146
     * on a UserDefinedForm extension.
147
     *
148
     * @return Form
149
     */
150
    public function Form()
151
    {
152
        $form = UserForm::create($this, 'Form_' . $this->ID);
153
        /** @skipUpgrade */
154
        $form->setFormAction(Controller::join_links($this->Link(), 'Form'));
155
        $this->generateConditionalJavascript();
156
        return $form;
157
    }
158
159
    /**
160
     * Generate the javascript for the conditional field show / hiding logic.
161
     *
162
     * @return void
163
     */
164
    public function generateConditionalJavascript()
165
    {
166
        $default = '';
0 ignored issues
show
The assignment to $default is dead and can be removed.
Loading history...
167
        $rules = '';
168
        $form = $this->data();
169
        $formFields = $form->Fields();
170
171
        $watch = [];
172
173
        if ($formFields) {
174
            /** @var EditableFormField $field */
175
            foreach ($formFields as $field) {
176
                if ($result = $field->formatDisplayRules()) {
177
                    $watch[] = $result;
178
                }
179
            }
180
        }
181
        if ($watch) {
182
            $rules .= $this->buildWatchJS($watch);
183
        }
184
185
        // Only add customScript if $default or $rules is defined
186
        if ($rules) {
187
            Requirements::customScript(<<<JS
188
                (function($) {
189
                    $(document).ready(function() {
190
                        {$rules}
191
                    });
192
                })(jQuery);
193
JS
194
            , 'UserFormsConditional-' . $form->ID);
195
        }
196
    }
197
198
    /**
199
     * Process the form that is submitted through the site
200
     *
201
     * {@see UserForm::validate()} for validation step prior to processing
202
     *
203
     * @param array $data
204
     * @param Form $form
205
     *
206
     * @return HTTPResponse
207
     */
208
    public function process($data, $form)
209
    {
210
        $submittedForm = SubmittedForm::create();
211
        $submittedForm->SubmittedByID = Security::getCurrentUser() ? Security::getCurrentUser()->ID : 0;
212
        $submittedForm->ParentClass = get_class($this->data());
213
        $submittedForm->ParentID = $this->ID;
214
215
        // if saving is not disabled save now to generate the ID
216
        if (!$this->DisableSaveSubmissions) {
217
            $submittedForm->write();
218
        }
219
220
        $attachments = [];
221
        $submittedFields = ArrayList::create();
222
223
        foreach ($this->data()->Fields() as $field) {
224
            if (!$field->showInReports()) {
225
                continue;
226
            }
227
228
            $submittedField = $field->getSubmittedFormField();
229
            $submittedField->ParentID = $submittedForm->ID;
230
            $submittedField->Name = $field->Name;
231
            $submittedField->Title = $field->getField('Title');
232
233
            // save the value from the data
234
            if ($field->hasMethod('getValueFromData')) {
235
                $submittedField->Value = $field->getValueFromData($data);
236
            } else {
237
                if (isset($data[$field->Name])) {
238
                    $submittedField->Value = $data[$field->Name];
239
                }
240
            }
241
242
            if (!empty($data[$field->Name])) {
243
                if (in_array(EditableFileField::class, $field->getClassAncestry())) {
244
                    if (!empty($_FILES[$field->Name]['name'])) {
245
                        $foldername = $field->getFormField()->getFolderName();
246
247
                        // create the file from post data
248
                        $upload = Upload::create();
249
                        $file = File::create();
250
                        $file->ShowInSearch = 0;
251
                        try {
252
                            $upload->loadIntoFile($_FILES[$field->Name], $file, $foldername);
253
                        } catch (ValidationException $e) {
254
                            $validationResult = $e->getResult();
255
                            $form->addErrorMessage($field->Name, $validationResult->message(), 'bad');
256
                            Controller::curr()->redirectBack();
257
                            return;
258
                        }
259
260
                        // write file to form field
261
                        $submittedField->UploadedFileID = $file->ID;
262
263
                        // attach a file only if lower than 1MB
264
                        if ($file->getAbsoluteSize() < 1024 * 1024 * 1) {
265
                            $attachments[] = $file;
266
                        }
267
                    }
268
                }
269
            }
270
271
            $submittedField->extend('onPopulationFromField', $field);
272
273
            if (!$this->DisableSaveSubmissions) {
274
                $submittedField->write();
275
            }
276
277
            $submittedFields->push($submittedField);
278
        }
279
280
        $emailData = [
281
            'Sender' => Security::getCurrentUser(),
282
            'HideFormData' => false,
283
            'Fields' => $submittedFields,
284
            'Body' => '',
285
        ];
286
287
        $this->extend('updateEmailData', $emailData, $attachments);
288
289
        // email users on submit.
290
        if ($recipients = $this->FilteredEmailRecipients($data, $form)) {
291
            foreach ($recipients as $recipient) {
292
                $email = Email::create()
293
                    ->setHTMLTemplate('email/SubmittedFormEmail')
294
                    ->setPlainTemplate('email/SubmittedFormEmail');
295
296
                // Merge fields are used for CMS authors to reference specific form fields in email content
297
                $mergeFields = $this->getMergeFieldsMap($emailData['Fields']);
298
299
                if ($attachments) {
300
                    foreach ($attachments as $file) {
301
                        /** @var File $file */
302
                        if ((int) $file->ID === 0) {
303
                            continue;
304
                        }
305
306
                        $email->addAttachmentFromData(
307
                            $file->getString(),
308
                            $file->getFilename(),
309
                            $file->getMimeType()
310
                        );
311
                    }
312
                }
313
314
                if (!$recipient->SendPlain && $recipient->emailTemplateExists()) {
315
                    $email->setHTMLTemplate($recipient->EmailTemplate);
316
                }
317
318
                // Add specific template data for the current recipient
319
                $emailData['HideFormData'] =  (bool) $recipient->HideFormData;
320
                // Include any parsed merge field references from the CMS editor - this is already escaped
321
                $emailData['Body'] = SSViewer::execute_string($recipient->getEmailBodyContent(), $mergeFields);
322
323
                // Push the template data to the Email's data
324
                foreach ($emailData as $key => $value) {
325
                    $email->addData($key, $value);
326
                }
327
328
                $email->setFrom(explode(',', $recipient->EmailFrom));
329
                $email->setTo(explode(',', $recipient->EmailAddress));
330
                $email->setSubject($recipient->EmailSubject);
331
332
                if ($recipient->EmailReplyTo) {
333
                    $email->setReplyTo(explode(',', $recipient->EmailReplyTo));
334
                }
335
336
                // check to see if they are a dynamic reply to. eg based on a email field a user selected
337
                if ($recipient->SendEmailFromField()) {
338
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailFromField()->Name);
339
340
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
341
                        $email->setReplyTo(explode(',', $submittedFormField->Value));
342
                    }
343
                }
344
                // check to see if they are a dynamic reciever eg based on a dropdown field a user selected
345
                if ($recipient->SendEmailToField()) {
346
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailToField()->Name);
347
348
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
349
                        $email->setTo(explode(',', $submittedFormField->Value));
350
                    }
351
                }
352
353
                // check to see if there is a dynamic subject
354
                if ($recipient->SendEmailSubjectField()) {
355
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailSubjectField()->Name);
356
357
                    if ($submittedFormField && trim($submittedFormField->Value)) {
358
                        $email->setSubject($submittedFormField->Value);
359
                    }
360
                }
361
362
                $this->extend('updateEmail', $email, $recipient, $emailData);
363
364
                if ((bool)$recipient->SendPlain) {
365
                    $body = strip_tags($recipient->getEmailBodyContent()) . "\n";
366
                    if (isset($emailData['Fields']) && !$emailData['HideFormData']) {
367
                        foreach ($emailData['Fields'] as $field) {
368
                            $body .= $field->Title . ': ' . $field->Value . " \n";
369
                        }
370
                    }
371
372
                    $email->setBody($body);
373
                    $email->sendPlain();
374
                } else {
375
                    $email->send();
376
                }
377
            }
378
        }
379
380
        $submittedForm->extend('updateAfterProcess');
381
382
        $session = $this->getRequest()->getSession();
383
        $session->clear("FormInfo.{$form->FormName()}.errors");
384
        $session->clear("FormInfo.{$form->FormName()}.data");
385
386
        $referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
387
388
        // set a session variable from the security ID to stop people accessing
389
        // the finished method directly.
390
        if (!$this->DisableAuthenicatedFinishAction) {
391
            if (isset($data['SecurityID'])) {
392
                $session->set('FormProcessed', $data['SecurityID']);
393
            } else {
394
                // if the form has had tokens disabled we still need to set FormProcessed
395
                // to allow us to get through the finshed method
396
                if (!$this->Form()->getSecurityToken()->isEnabled()) {
397
                    $randNum = rand(1, 1000);
398
                    $randHash = md5($randNum);
399
                    $session->set('FormProcessed', $randHash);
400
                    $session->set('FormProcessedNum', $randNum);
401
                }
402
            }
403
        }
404
405
        if (!$this->DisableSaveSubmissions) {
406
            $session->set('userformssubmission'. $this->ID, $submittedForm->ID);
407
        }
408
409
        return $this->redirect($this->Link('finished') . $referrer . $this->config()->get('finished_anchor'));
410
    }
411
412
    /**
413
     * Allows the use of field values in email body.
414
     *
415
     * @param ArrayList $fields
416
     * @return ArrayData
417
     */
418
    protected function getMergeFieldsMap($fields = [])
419
    {
420
        $data = ArrayData::create([]);
421
422
        foreach ($fields as $field) {
423
            $data->setField($field->Name, DBField::create_field('Text', $field->Value));
424
        }
425
426
        return $data;
427
    }
428
429
    /**
430
     * This action handles rendering the "finished" message, which is
431
     * customizable by editing the ReceivedFormSubmission template.
432
     *
433
     * @return ViewableData
434
     */
435
    public function finished()
436
    {
437
        $submission = $this->getRequest()->getSession()->get('userformssubmission'. $this->ID);
438
439
        if ($submission) {
440
            $submission = SubmittedForm::get()->byId($submission);
441
        }
442
443
        $referrer = isset($_GET['referrer']) ? urldecode($_GET['referrer']) : null;
444
445
        if (!$this->DisableAuthenicatedFinishAction) {
446
            $formProcessed = $this->getRequest()->getSession()->get('FormProcessed');
447
448
            if (!isset($formProcessed)) {
449
                return $this->redirect($this->Link() . $referrer);
450
            } else {
451
                $securityID = $this->getRequest()->getSession()->get('SecurityID');
452
                // make sure the session matches the SecurityID and is not left over from another form
453
                if ($formProcessed != $securityID) {
454
                    // they may have disabled tokens on the form
455
                    $securityID = md5($this->getRequest()->getSession()->get('FormProcessedNum'));
456
                    if ($formProcessed != $securityID) {
457
                        return $this->redirect($this->Link() . $referrer);
458
                    }
459
                }
460
            }
461
462
            $this->getRequest()->getSession()->clear('FormProcessed');
463
        }
464
465
        $data = [
466
            'Submission' => $submission,
467
            'Link' => $referrer
468
        ];
469
470
        $this->extend('updateReceivedFormSubmissionData', $data);
471
472
        return $this->customise([
473
            'Content' => $this->customise($data)->renderWith(__CLASS__ . '_ReceivedFormSubmission'),
474
            'Form' => '',
475
        ]);
476
    }
477
478
    /**
479
     * Outputs the required JS from the $watch input
480
     *
481
     * @param array $watch
482
     *
483
     * @return string
484
     */
485
    protected function buildWatchJS($watch)
486
    {
487
        $result = '';
488
        foreach ($watch as $key => $rule) {
489
            $events = implode(' ', $rule['events']);
490
            $selectors = implode(', ', $rule['selectors']);
491
            $conjunction = $rule['conjunction'];
492
            $operations = implode(" {$conjunction} ", $rule['operations']);
493
            $target = $rule['targetFieldID'];
494
495
            $result .= <<<EOS
496
\n
497
    $('.userform').on('{$events}',
498
    "{$selectors}",
499
    function (){
500
        if ({$operations}) {
501
            $('{$target}').{$rule['view']};
502
        } else {
503
            $('{$target}').{$rule['opposite']};
504
        }
505
    });
506
    $("{$target}").find('.hide').removeClass('hide');
507
EOS;
508
        }
509
510
        return $result;
511
    }
512
}
513