Completed
Push — master ( 177bd3...57cd0b )
by Will
12s
created

code/Control/UserDefinedFormController.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\HTTP;
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\ArrayData;
22
use SilverStripe\View\Requirements;
23
use SilverStripe\View\SSViewer;
24
25
/**
26
 * Controller for the {@link UserDefinedForm} page type.
27
 *
28
 * @package userforms
29
 */
30
class UserDefinedFormController extends PageController
31
{
32
    private static $finished_anchor = '#uff';
33
34
    private static $allowed_actions = [
35
        'index',
36
        'ping',
37
        'Form',
38
        'finished'
39
    ];
40
41
    protected function init()
42
    {
43
        parent::init();
44
45
        $page = $this->data();
46
47
        // load the css
48
        if (!$page->config()->get('block_default_userforms_css')) {
49
            Requirements::css('silverstripe/userforms:client/dist/styles/userforms.css');
50
        }
51
52
        // load the jquery
53
        if (!$page->config()->get('block_default_userforms_js')) {
54
            Requirements::javascript('//code.jquery.com/jquery-1.7.2.min.js');
55
            Requirements::javascript(
56
                'silverstripe/userforms:client/thirdparty/jquery-validate/jquery.validate.min.js'
57
            );
58
            Requirements::add_i18n_javascript('silverstripe/userforms:client/lang');
59
            Requirements::javascript('silverstripe/userforms:client/dist/js/userforms.js');
60
61
            $this->addUserFormsValidatei18n();
62
63
            // Bind a confirmation message when navigating away from a partially completed form.
64
            if ($page::config()->get('enable_are_you_sure')) {
65
                Requirements::javascript(
66
                    'silverstripe/userforms:client/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js'
67
                );
68
            }
69
        }
70
    }
71
72
    /**
73
     * Add the necessary jQuery validate i18n translation files, either by locale or by langauge,
74
     * e.g. 'en_NZ' or 'en'. This adds "methods_abc.min.js" as well as "messages_abc.min.js" from the
75
     * jQuery validate thirdparty library.
76
     */
77
    protected function addUserFormsValidatei18n()
78
    {
79
        $module = ModuleLoader::getModule('silverstripe/userforms');
80
81
        $candidates = [
82
            i18n::getData()->langFromLocale(i18n::config()->get('default_locale')),
83
            i18n::config()->get('default_locale'),
84
            i18n::getData()->langFromLocale(i18n::get_locale()),
85
            i18n::get_locale(),
86
        ];
87
88
        foreach ($candidates as $candidate) {
89
            foreach (['messages', 'methods'] as $candidateType) {
90
                $localisationCandidate = "client/thirdparty/jquery-validate/localization/{$candidateType}_{$candidate}.min.js";
91
92
                $resource = $module->getResource($localisationCandidate);
0 ignored issues
show
The method getResource() does not exist on SilverStripe\Core\Manifest\Module. Did you maybe mean getResourcePath()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
93
                if ($resource->exists()) {
94
                    Requirements::javascript($resource->getRelativePath());
95
                }
96
            }
97
        }
98
    }
99
100
    /**
101
     * Using $UserDefinedForm in the Content area of the page shows
102
     * where the form should be rendered into. If it does not exist
103
     * then default back to $Form.
104
     *
105
     * @return array
106
     */
107
    public function index()
108
    {
109
        if ($this->Content && $form = $this->Form()) {
110
            $hasLocation = stristr($this->Content, '$UserDefinedForm');
111
            if ($hasLocation) {
112
                /** @see Requirements_Backend::escapeReplacement */
113
                $formEscapedForRegex = addcslashes($form->forTemplate(), '\\$');
114
                $content = preg_replace(
115
                    '/(<p[^>]*>)?\\$UserDefinedForm(<\\/p>)?/i',
116
                    $formEscapedForRegex,
117
                    $this->Content
118
                );
119
                return [
120
                    'Content' => DBField::create_field('HTMLText', $content),
121
                    'Form' => ''
122
                ];
123
            }
124
        }
125
126
        return [
127
            'Content' => DBField::create_field('HTMLText', $this->Content),
128
            'Form' => $this->Form()
129
        ];
130
    }
131
132
    /**
133
     * Keep the session alive for the user.
134
     *
135
     * @return int
136
     */
137
    public function ping()
138
    {
139
        return 1;
140
    }
141
142
    /**
143
     * Get the form for the page. Form can be modified by calling {@link updateForm()}
144
     * on a UserDefinedForm extension.
145
     *
146
     * @return Form
147
     */
148
    public function Form()
149
    {
150
        $form = UserForm::create($this, 'Form_' . $this->ID);
151
        /** @skipUpgrade */
152
        $form->setFormAction(Controller::join_links($this->Link(), 'Form'));
153
        $this->generateConditionalJavascript();
154
        return $form;
155
    }
156
157
    /**
158
     * Generate the javascript for the conditional field show / hiding logic.
159
     *
160
     * @return void
161
     */
162
    public function generateConditionalJavascript()
163
    {
164
        $default = '';
165
        $rules = '';
166
167
        $watch = [];
168
169
        if ($this->Fields()) {
170
            /** @var EditableFormField $field */
171
            foreach ($this->Fields() as $field) {
172
                if ($result = $field->formatDisplayRules()) {
173
                    $watch[] = $result;
174
                }
175
            }
176
        }
177
        if ($watch) {
178
            $rules .= $this->buildWatchJS($watch);
179
        }
180
181
        // Only add customScript if $default or $rules is defined
182
        if ($rules) {
183
            Requirements::customScript(<<<JS
184
                (function($) {
185
                    $(document).ready(function() {
186
                        {$rules}
187
                    });
188
                })(jQuery);
189
JS
190
            , 'UserFormsConditional');
191
        }
192
    }
193
194
    /**
195
     * Process the form that is submitted through the site
196
     *
197
     * {@see UserForm::validate()} for validation step prior to processing
198
     *
199
     * @param array $data
200
     * @param Form $form
201
     *
202
     * @return \SilverStripe\Control\HTTPResponse
203
     */
204
    public function process($data, $form)
205
    {
206
        $submittedForm = SubmittedForm::create();
207
        $submittedForm->SubmittedByID = Security::getCurrentUser() ? Security::getCurrentUser()->ID : 0;
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 = array();
216
        $submittedFields = ArrayList::create();
217
218
        foreach ($this->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
            'Fields' => $submittedFields
278
        ];
279
280
        $this->extend('updateEmailData', $emailData, $attachments);
281
282
        // email users on submit.
283
        if ($recipients = $this->FilteredEmailRecipients($data, $form)) {
284
            foreach ($recipients as $recipient) {
285
                $email = Email::create()
286
                    ->setHTMLTemplate('email/SubmittedFormEmail.ss')
287
                    ->setPlainTemplate('email/SubmittedFormEmail.ss');
288
289
                $mergeFields = $this->getMergeFieldsMap($emailData['Fields']);
290
291
                if ($attachments) {
292
                    foreach ($attachments as $file) {
293
                        if (!$file->ID != 0) {
294
                            continue;
295
                        }
296
297
                        $email->attachFile(
298
                            $file->Filename,
299
                            $file->Filename,
300
                            HTTP::get_mime_type($file->Filename)
301
                        );
302
                    }
303
                }
304
305
                $parsedBody = SSViewer::execute_string($recipient->getEmailBodyContent(), $mergeFields);
306
307
                if (!$recipient->SendPlain && $recipient->emailTemplateExists()) {
308
                    $email->setHTMLTemplate($recipient->EmailTemplate);
309
                }
310
311
                $email->setData($recipient);
312
                foreach ($emailData as $key => $value) {
313
                    $email->addData($key, $value);
314
                }
315
316
                $email->setFrom($recipient->EmailFrom);
317
                $email->setBody($parsedBody);
318
                $email->setTo($recipient->EmailAddress);
319
                $email->setSubject($recipient->EmailSubject);
320
321
                if ($recipient->EmailReplyTo) {
322
                    $email->setReplyTo($recipient->EmailReplyTo);
323
                }
324
325
                // check to see if they are a dynamic reply to. eg based on a email field a user selected
326 View Code Duplication
                if ($recipient->SendEmailFromField()) {
327
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailFromField()->Name);
328
329
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
330
                        $email->setReplyTo($submittedFormField->Value);
331
                    }
332
                }
333
                // check to see if they are a dynamic reciever eg based on a dropdown field a user selected
334 View Code Duplication
                if ($recipient->SendEmailToField()) {
335
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailToField()->Name);
336
337
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
338
                        $email->setTo($submittedFormField->Value);
339
                    }
340
                }
341
342
                // check to see if there is a dynamic subject
343 View Code Duplication
                if ($recipient->SendEmailSubjectField()) {
344
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailSubjectField()->Name);
345
346
                    if ($submittedFormField && trim($submittedFormField->Value)) {
347
                        $email->setSubject($submittedFormField->Value);
348
                    }
349
                }
350
351
                $this->extend('updateEmail', $email, $recipient, $emailData);
352
353
                if ($recipient->SendPlain) {
354
                    $body = strip_tags($recipient->getEmailBodyContent()) . "\n";
355
                    if (isset($emailData['Fields']) && !$recipient->HideFormData) {
356
                        foreach ($emailData['Fields'] as $Field) {
357
                            $body .= $Field->Title . ': ' . $Field->Value . " \n";
358
                        }
359
                    }
360
361
                    $email->setBody($body);
362
                    $email->sendPlain();
363
                } else {
364
                    $email->send();
365
                }
366
            }
367
        }
368
369
        $submittedForm->extend('updateAfterProcess');
370
371
        $session = $this->getRequest()->getSession();
372
        $session->clear("FormInfo.{$form->FormName()}.errors");
373
        $session->clear("FormInfo.{$form->FormName()}.data");
374
375
        $referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
376
377
        // set a session variable from the security ID to stop people accessing
378
        // the finished method directly.
379
        if (!$this->DisableAuthenicatedFinishAction) {
380
            if (isset($data['SecurityID'])) {
381
                $session->set('FormProcessed', $data['SecurityID']);
382
            } else {
383
                // if the form has had tokens disabled we still need to set FormProcessed
384
                // to allow us to get through the finshed method
385
                if (!$this->Form()->getSecurityToken()->isEnabled()) {
386
                    $randNum = rand(1, 1000);
387
                    $randHash = md5($randNum);
388
                    $session->set('FormProcessed', $randHash);
389
                    $session->set('FormProcessedNum', $randNum);
390
                }
391
            }
392
        }
393
394
        if (!$this->DisableSaveSubmissions) {
395
            $session->set('userformssubmission'. $this->ID, $submittedForm->ID);
396
        }
397
398
        return $this->redirect($this->Link('finished') . $referrer . $this->config()->get('finished_anchor'));
399
    }
400
401
    /**
402
     * Allows the use of field values in email body.
403
     *
404
     * @param ArrayList $fields
405
     * @return ArrayData
406
     */
407
    protected function getMergeFieldsMap($fields = [])
408
    {
409
        $data = ArrayData::create([]);
410
411
        foreach ($fields as $field) {
412
            $data->setField($field->Name, DBField::create_field('Text', $field->Value));
413
        }
414
415
        return $data;
416
    }
417
418
    /**
419
     * This action handles rendering the "finished" message, which is
420
     * customizable by editing the ReceivedFormSubmission template.
421
     *
422
     * @return ViewableData
423
     */
424
    public function finished()
425
    {
426
        $submission = $this->getRequest()->getSession()->get('userformssubmission'. $this->ID);
427
428
        if ($submission) {
429
            $submission = SubmittedForm::get()->byId($submission);
430
        }
431
432
        $referrer = isset($_GET['referrer']) ? urldecode($_GET['referrer']) : null;
433
434
        if (!$this->DisableAuthenicatedFinishAction) {
435
            $formProcessed = $this->getRequest()->getSession()->get('FormProcessed');
436
437
            if (!isset($formProcessed)) {
438
                return $this->redirect($this->Link() . $referrer);
439
            } else {
440
                $securityID = $this->getRequest()->getSession()->get('SecurityID');
441
                // make sure the session matches the SecurityID and is not left over from another form
442
                if ($formProcessed != $securityID) {
443
                    // they may have disabled tokens on the form
444
                    $securityID = md5($this->getRequest()->getSession()->get('FormProcessedNum'));
445
                    if ($formProcessed != $securityID) {
446
                        return $this->redirect($this->Link() . $referrer);
447
                    }
448
                }
449
            }
450
451
            $this->getRequest()->getSession()->clear('FormProcessed');
452
        }
453
454
        $data = [
455
            'Submission' => $submission,
456
            'Link' => $referrer
457
        ];
458
459
        $this->extend('updateReceivedFormSubmissionData', $data);
460
461
        return $this->customise([
462
            'Content' => $this->customise($data)->renderWith(__CLASS__ . '_ReceivedFormSubmission'),
463
            'Form' => '',
464
        ]);
465
    }
466
467
    /**
468
     * Outputs the required JS from the $watch input
469
     *
470
     * @param array $watch
471
     *
472
     * @return string
473
     */
474
    protected function buildWatchJS($watch)
475
    {
476
        $result = '';
477
        foreach ($watch as $key => $rule) {
478
            $events = implode(' ', $rule['events']);
479
            $selectors = implode(', ', $rule['selectors']);
480
            $conjunction = $rule['conjunction'];
481
            $operations = implode(" {$conjunction} ", $rule['operations']);
482
            $target = $rule['targetFieldID'];
483
484
            $result .= <<<EOS
485
\n
486
    $('.userform').on('{$events}',
487
    "{$selectors}",
488
    function (){
489
        if ({$operations}) {
490
            $('{$target}').{$rule['view']};
491
        } else {
492
            $('{$target}').{$rule['opposite']};
493
        }
494
    });
495
    $("{$target}").find('.hide').removeClass('hide');
496
EOS;
497
        }
498
499
        return $result;
500
    }
501
}
502