Passed
Pull Request — master (#688)
by Robbie
03:46
created

UserDefinedFormController::ping()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\UserForms\Control;
4
5
use PageController;
0 ignored issues
show
Bug introduced by
The type PageController was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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';
0 ignored issues
show
introduced by
The private property $finished_anchor is not used, and could be removed.
Loading history...
33
34
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
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::javascript('silverstripe/admin:client/dist/js/i18n.js');
59
            Requirements::add_i18n_javascript('silverstripe/userforms:client/lang');
60
            Requirements::javascript('silverstripe/userforms:client/dist/js/userforms.js');
61
62
            $this->addUserFormsValidatei18n();
63
64
            // Bind a confirmation message when navigating away from a partially completed form.
65
            if ($page::config()->get('enable_are_you_sure')) {
66
                Requirements::javascript(
67
                    'silverstripe/userforms:client/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js'
68
                );
69
            }
70
        }
71
    }
72
73
    /**
74
     * Add the necessary jQuery validate i18n translation files, either by locale or by langauge,
75
     * e.g. 'en_NZ' or 'en'. This adds "methods_abc.min.js" as well as "messages_abc.min.js" from the
76
     * jQuery validate thirdparty library.
77
     */
78
    protected function addUserFormsValidatei18n()
79
    {
80
        $module = ModuleLoader::getModule('silverstripe/userforms');
81
82
        $candidates = [
83
            i18n::getData()->langFromLocale(i18n::config()->get('default_locale')),
84
            i18n::config()->get('default_locale'),
85
            i18n::getData()->langFromLocale(i18n::get_locale()),
86
            i18n::get_locale(),
87
        ];
88
89
        foreach ($candidates as $candidate) {
90
            foreach (['messages', 'methods'] as $candidateType) {
91
                $localisationCandidate = "client/thirdparty/jquery-validate/localization/{$candidateType}_{$candidate}.min.js";
92
93
                $resource = $module->getResource($localisationCandidate);
94
                if ($resource->exists()) {
95
                    Requirements::javascript($resource->getRelativePath());
96
                }
97
            }
98
        }
99
    }
100
101
    /**
102
     * Using $UserDefinedForm in the Content area of the page shows
103
     * where the form should be rendered into. If it does not exist
104
     * then default back to $Form.
105
     *
106
     * @return array
107
     */
108
    public function index()
109
    {
110
        if ($this->Content && $form = $this->Form()) {
111
            $hasLocation = stristr($this->Content, '$UserDefinedForm');
112
            if ($hasLocation) {
113
                /** @see Requirements_Backend::escapeReplacement */
114
                $formEscapedForRegex = addcslashes($form->forTemplate(), '\\$');
115
                $content = preg_replace(
116
                    '/(<p[^>]*>)?\\$UserDefinedForm(<\\/p>)?/i',
117
                    $formEscapedForRegex,
118
                    $this->Content
119
                );
120
                return [
121
                    'Content' => DBField::create_field('HTMLText', $content),
122
                    'Form' => ''
123
                ];
124
            }
125
        }
126
127
        return [
128
            'Content' => DBField::create_field('HTMLText', $this->Content),
129
            'Form' => $this->Form()
130
        ];
131
    }
132
133
    /**
134
     * Keep the session alive for the user.
135
     *
136
     * @return int
137
     */
138
    public function ping()
139
    {
140
        return 1;
141
    }
142
143
    /**
144
     * Get the form for the page. Form can be modified by calling {@link updateForm()}
145
     * on a UserDefinedForm extension.
146
     *
147
     * @return Form
148
     */
149
    public function Form()
150
    {
151
        $form = UserForm::create($this, 'Form_' . $this->ID);
0 ignored issues
show
Bug introduced by
$this of type SilverStripe\UserForms\C...erDefinedFormController is incompatible with the type array expected by parameter $args of SilverStripe\View\ViewableData::create(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

151
        $form = UserForm::create(/** @scrutinizer ignore-type */ $this, 'Form_' . $this->ID);
Loading history...
Bug introduced by
'Form_' . $this->ID of type string is incompatible with the type array expected by parameter $args of SilverStripe\View\ViewableData::create(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

151
        $form = UserForm::create($this, /** @scrutinizer ignore-type */ 'Form_' . $this->ID);
Loading history...
152
        /** @skipUpgrade */
153
        $form->setFormAction(Controller::join_links($this->Link(), 'Form'));
154
        $this->generateConditionalJavascript();
155
        return $form;
156
    }
157
158
    /**
159
     * Generate the javascript for the conditional field show / hiding logic.
160
     *
161
     * @return void
162
     */
163
    public function generateConditionalJavascript()
164
    {
165
        $default = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $default is dead and can be removed.
Loading history...
166
        $rules = '';
167
168
        $watch = [];
169
170
        if ($this->data()->Fields()) {
171
            /** @var EditableFormField $field */
172
            foreach ($this->data()->Fields() as $field) {
173
                if ($result = $field->formatDisplayRules()) {
174
                    $watch[] = $result;
175
                }
176
            }
177
        }
178
        if ($watch) {
179
            $rules .= $this->buildWatchJS($watch);
180
        }
181
182
        // Only add customScript if $default or $rules is defined
183
        if ($rules) {
184
            Requirements::customScript(<<<JS
185
                (function($) {
186
                    $(document).ready(function() {
187
                        {$rules}
188
                    });
189
                })(jQuery);
190
JS
191
            , 'UserFormsConditional');
192
        }
193
    }
194
195
    /**
196
     * Process the form that is submitted through the site
197
     *
198
     * {@see UserForm::validate()} for validation step prior to processing
199
     *
200
     * @param array $data
201
     * @param Form $form
202
     *
203
     * @return \SilverStripe\Control\HTTPResponse
204
     */
205
    public function process($data, $form)
206
    {
207
        $submittedForm = SubmittedForm::create();
208
        $submittedForm->SubmittedByID = Security::getCurrentUser() ? Security::getCurrentUser()->ID : 0;
209
        $submittedForm->ParentClass = get_class($this->data());
210
        $submittedForm->ParentID = $this->ID;
211
212
        // if saving is not disabled save now to generate the ID
213
        if (!$this->DisableSaveSubmissions) {
214
            $submittedForm->write();
215
        }
216
217
        $attachments = array();
218
        $submittedFields = ArrayList::create();
219
220
        foreach ($this->data()->Fields() as $field) {
221
            if (!$field->showInReports()) {
222
                continue;
223
            }
224
225
            $submittedField = $field->getSubmittedFormField();
226
            $submittedField->ParentID = $submittedForm->ID;
227
            $submittedField->Name = $field->Name;
228
            $submittedField->Title = $field->getField('Title');
229
230
            // save the value from the data
231
            if ($field->hasMethod('getValueFromData')) {
232
                $submittedField->Value = $field->getValueFromData($data);
233
            } else {
234
                if (isset($data[$field->Name])) {
235
                    $submittedField->Value = $data[$field->Name];
236
                }
237
            }
238
239
            if (!empty($data[$field->Name])) {
240
                if (in_array(EditableFileField::class, $field->getClassAncestry())) {
241
                    if (!empty($_FILES[$field->Name]['name'])) {
242
                        $foldername = $field->getFormField()->getFolderName();
243
244
                        // create the file from post data
245
                        $upload = Upload::create();
246
                        $file = File::create();
247
                        $file->ShowInSearch = 0;
248
                        try {
249
                            $upload->loadIntoFile($_FILES[$field->Name], $file, $foldername);
250
                        } catch (ValidationException $e) {
251
                            $validationResult = $e->getResult();
252
                            $form->addErrorMessage($field->Name, $validationResult->message(), 'bad');
0 ignored issues
show
Bug introduced by
The method message() does not exist on SilverStripe\ORM\ValidationResult. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

252
                            $form->addErrorMessage($field->Name, $validationResult->/** @scrutinizer ignore-call */ message(), 'bad');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
253
                            Controller::curr()->redirectBack();
254
                            return;
255
                        }
256
257
                        // write file to form field
258
                        $submittedField->UploadedFileID = $file->ID;
259
260
                        // attach a file only if lower than 1MB
261
                        if ($file->getAbsoluteSize() < 1024 * 1024 * 1) {
262
                            $attachments[] = $file;
263
                        }
264
                    }
265
                }
266
            }
267
268
            $submittedField->extend('onPopulationFromField', $field);
269
270
            if (!$this->DisableSaveSubmissions) {
271
                $submittedField->write();
272
            }
273
274
            $submittedFields->push($submittedField);
275
        }
276
277
        $emailData = [
278
            'Sender' => Security::getCurrentUser(),
279
            'Fields' => $submittedFields
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.ss')
289
                    ->setPlainTemplate('email/SubmittedFormEmail.ss');
290
291
                $mergeFields = $this->getMergeFieldsMap($emailData['Fields']);
292
293
                if ($attachments) {
294
                    foreach ($attachments as $file) {
295
                        if (!$file->ID != 0) {
296
                            continue;
297
                        }
298
299
                        $email->attachFile(
300
                            $file->Filename,
301
                            $file->Filename,
302
                            HTTP::get_mime_type($file->Filename)
303
                        );
304
                    }
305
                }
306
307
                $parsedBody = SSViewer::execute_string($recipient->getEmailBodyContent(), $mergeFields);
308
309
                if (!$recipient->SendPlain && $recipient->emailTemplateExists()) {
310
                    $email->setHTMLTemplate($recipient->EmailTemplate);
311
                }
312
313
                $email->setData($recipient);
314
                foreach ($emailData as $key => $value) {
315
                    $email->addData($key, $value);
316
                }
317
318
                $email->setFrom($recipient->EmailFrom);
319
                $email->setBody($parsedBody);
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 View Code Duplication
                if ($recipient->SendEmailFromField()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
                if ($recipient->SendEmailToField()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
                if ($recipient->SendEmailSubjectField()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 ($recipient->SendPlain) {
356
                    $body = strip_tags($recipient->getEmailBodyContent()) . "\n";
357
                    if (isset($emailData['Fields']) && !$recipient->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
     * Allows the use of field values in email body.
405
     *
406
     * @param ArrayList $fields
407
     * @return ArrayData
408
     */
409
    protected function getMergeFieldsMap($fields = [])
410
    {
411
        $data = ArrayData::create([]);
412
413
        foreach ($fields as $field) {
414
            $data->setField($field->Name, DBField::create_field('Text', $field->Value));
415
        }
416
417
        return $data;
418
    }
419
420
    /**
421
     * This action handles rendering the "finished" message, which is
422
     * customizable by editing the ReceivedFormSubmission template.
423
     *
424
     * @return ViewableData
0 ignored issues
show
Bug introduced by
The type SilverStripe\UserForms\Control\ViewableData was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
425
     */
426
    public function finished()
427
    {
428
        $submission = $this->getRequest()->getSession()->get('userformssubmission'. $this->ID);
429
430
        if ($submission) {
431
            $submission = SubmittedForm::get()->byId($submission);
432
        }
433
434
        $referrer = isset($_GET['referrer']) ? urldecode($_GET['referrer']) : null;
435
436
        if (!$this->DisableAuthenicatedFinishAction) {
437
            $formProcessed = $this->getRequest()->getSession()->get('FormProcessed');
438
439
            if (!isset($formProcessed)) {
440
                return $this->redirect($this->Link() . $referrer);
441
            } else {
442
                $securityID = $this->getRequest()->getSession()->get('SecurityID');
443
                // make sure the session matches the SecurityID and is not left over from another form
444
                if ($formProcessed != $securityID) {
445
                    // they may have disabled tokens on the form
446
                    $securityID = md5($this->getRequest()->getSession()->get('FormProcessedNum'));
447
                    if ($formProcessed != $securityID) {
448
                        return $this->redirect($this->Link() . $referrer);
449
                    }
450
                }
451
            }
452
453
            $this->getRequest()->getSession()->clear('FormProcessed');
454
        }
455
456
        $data = [
457
            'Submission' => $submission,
458
            'Link' => $referrer
459
        ];
460
461
        $this->extend('updateReceivedFormSubmissionData', $data);
462
463
        return $this->customise([
464
            'Content' => $this->customise($data)->renderWith(__CLASS__ . '_ReceivedFormSubmission'),
465
            'Form' => '',
466
        ]);
467
    }
468
469
    /**
470
     * Outputs the required JS from the $watch input
471
     *
472
     * @param array $watch
473
     *
474
     * @return string
475
     */
476
    protected function buildWatchJS($watch)
477
    {
478
        $result = '';
479
        foreach ($watch as $key => $rule) {
480
            $events = implode(' ', $rule['events']);
481
            $selectors = implode(', ', $rule['selectors']);
482
            $conjunction = $rule['conjunction'];
483
            $operations = implode(" {$conjunction} ", $rule['operations']);
484
            $target = $rule['targetFieldID'];
485
486
            $result .= <<<EOS
487
\n
488
    $('.userform').on('{$events}',
489
    "{$selectors}",
490
    function (){
491
        if ({$operations}) {
492
            $('{$target}').{$rule['view']};
493
        } else {
494
            $('{$target}').{$rule['opposite']};
495
        }
496
    });
497
    $("{$target}").find('.hide').removeClass('hide');
498
EOS;
499
        }
500
501
        return $result;
502
    }
503
}
504