Passed
Pull Request — master (#704)
by Robbie
04:05
created

UserDefinedFormController::getMergeFieldsMap()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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