Completed
Pull Request — master (#647)
by Robbie
13:08
created

UserDefinedFormController::process()   F

Complexity

Conditions 40
Paths 4108

Size

Total Lines 196
Code Lines 110

Duplication

Lines 21
Ratio 10.71 %

Importance

Changes 0
Metric Value
dl 21
loc 196
rs 2
c 0
b 0
f 0
cc 40
eloc 110
nc 4108
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\UserForms\Model;
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';
0 ignored issues
show
Unused Code introduced by
The property $finished_anchor is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
33
34
    private static $allowed_actions = [
0 ignored issues
show
Unused Code introduced by
The property $allowed_actions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can 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
        $userforms = ModuleLoader::getModule('silverstripe/userforms');
48
        $admin = ModuleLoader::getModule('silverstripe/admin');
49
        // load the css
50
        if (!$page->config()->get('block_default_userforms_css')) {
51
            Requirements::css($userforms->getRelativeResourcePath('css/UserForm.css'));
52
        }
53
54
        // load the jquery
55
        if (!$page->config()->get('block_default_userforms_js')) {
56
            $lang = i18n::getData()->languageName(i18n::get_locale());
0 ignored issues
show
Unused Code introduced by
$lang is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
57
58
            Requirements::javascript($admin->getRelativeResourcePath('thirdparty/jquery/jquery.js'));
59
            Requirements::javascript(
60
                $userforms->getRelativeResourcePath('thirdparty/jquery-validate/jquery.validate.min.js')
61
            );
62
            Requirements::add_i18n_javascript($userforms->getRelativeResourcePath('javascript/lang'));
63
            Requirements::javascript($userforms->getRelativeResourcePath('javascript/UserForm.js'));
64
65
            // @todo implement the $lang correctly
66
            // Requirements::javascript(
67
            //     $userforms->getRelativeResourcePath("thirdparty/jquery-validate/localization/messages_{$lang}.min.js")
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
68
            // );
69
            // Requirements::javascript(
70
            //     $userforms->getRelativeResourcePath("thirdparty/jquery-validate/localization/methods_{$lang}.min.js")
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
71
            // );
72
73
            // Bind a confirmation message when navigating away from a partially completed form.
74
            if ($page::config()->get('enable_are_you_sure')) {
75
                Requirements::javascript(
76
                    $userforms->getRelativeResourcePath('thirdparty/jquery.are-you-sure/jquery.are-you-sure.js')
77
                );
78
            }
79
        }
80
    }
81
82
    /**
83
     * Using $UserDefinedForm in the Content area of the page shows
84
     * where the form should be rendered into. If it does not exist
85
     * then default back to $Form.
86
     *
87
     * @return array
88
     */
89
    public function index()
90
    {
91
        if ($this->Content && $form = $this->Form()) {
92
            $hasLocation = stristr($this->Content, '$UserDefinedForm');
93
            if ($hasLocation) {
94
                $content = preg_replace(
95
                    '/(<p[^>]*>)?\\$UserDefinedForm(<\\/p>)?/i',
96
                    $form->forTemplate(),
97
                    $this->Content
98
                );
99
                return [
100
                    'Content' => DBField::create_field('HTMLText', $content),
101
                    'Form' => ''
102
                ];
103
            }
104
        }
105
106
        return [
107
            'Content' => DBField::create_field('HTMLText', $this->Content),
108
            'Form' => $this->Form()
109
        ];
110
    }
111
112
    /**
113
     * Keep the session alive for the user.
114
     *
115
     * @return int
116
     */
117
    public function ping()
118
    {
119
        return 1;
120
    }
121
122
    /**
123
     * Get the form for the page. Form can be modified by calling {@link updateForm()}
124
     * on a UserDefinedForm extension.
125
     *
126
     * @return Forms
127
     */
128
    public function Form()
129
    {
130
        $form = UserForm::create($this, 'Form_' . $this->ID);
131
        /** @skipUpgrade */
132
        $form->setFormAction(Controller::join_links($this->Link(), 'Form'));
133
        $this->generateConditionalJavascript();
134
        return $form;
135
    }
136
137
    /**
138
     * Generate the javascript for the conditional field show / hiding logic.
139
     *
140
     * @return void
141
     */
142
    public function generateConditionalJavascript()
143
    {
144
        $default = '';
0 ignored issues
show
Unused Code introduced by
$default is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
145
        $rules = '';
146
147
        $watch = [];
148
149
        if ($this->Fields()) {
150
            /** @var EditableFormField $field */
151
            foreach ($this->Fields() as $field) {
152
                if ($result = $field->formatDisplayRules()) {
153
                    $watch[] = $result;
154
                }
155
            }
156
        }
157
        if ($watch) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $watch of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
158
            $rules .= $this->buildWatchJS($watch);
159
        }
160
161
        // Only add customScript if $default or $rules is defined
162
        if ($rules) {
163
            Requirements::customScript(<<<JS
164
                (function($) {
165
                    $(document).ready(function() {
166
                        {$rules}
167
                    });
168
                })(jQuery);
169
JS
170
            , 'UserFormsConditional');
171
        }
172
    }
173
174
    /**
175
     * Process the form that is submitted through the site
176
     *
177
     * {@see UserForm::validate()} for validation step prior to processing
178
     *
179
     * @param array $data
180
     * @param Form $form
181
     *
182
     * @return Redirection
183
     */
184
    public function process($data, $form)
0 ignored issues
show
Coding Style introduced by
process uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
185
    {
186
        $submittedForm = SubmittedForm::create();
187
        $submittedForm->SubmittedByID = Security::getCurrentUser() ? Security::getCurrentUser()->ID : 0;
188
        $submittedForm->ParentID = $this->ID;
189
190
        // if saving is not disabled save now to generate the ID
191
        if (!$this->DisableSaveSubmissions) {
192
            $submittedForm->write();
193
        }
194
195
        $attachments = array();
196
        $submittedFields = ArrayList::create();
197
198
        foreach ($this->Fields() as $field) {
199
            if (!$field->showInReports()) {
200
                continue;
201
            }
202
203
            $submittedField = $field->getSubmittedFormField();
204
            $submittedField->ParentID = $submittedForm->ID;
205
            $submittedField->Name = $field->Name;
206
            $submittedField->Title = $field->getField('Title');
207
208
            // save the value from the data
209
            if ($field->hasMethod('getValueFromData')) {
210
                $submittedField->Value = $field->getValueFromData($data);
211
            } else {
212
                if (isset($data[$field->Name])) {
213
                    $submittedField->Value = $data[$field->Name];
214
                }
215
            }
216
217
            if (!empty($data[$field->Name])) {
218
                if (in_array(EditableFileField::class, $field->getClassAncestry())) {
219
                    if (!empty($_FILES[$field->Name]['name'])) {
220
                        $foldername = $field->getFormField()->getFolderName();
221
222
                        // create the file from post data
223
                        $upload = Upload::create();
224
                        $file = File::create();
225
                        $file->ShowInSearch = 0;
226
                        try {
227
                            $upload->loadIntoFile($_FILES[$field->Name], $file, $foldername);
228
                        } catch (ValidationException $e) {
229
                            $validationResult = $e->getResult();
230
                            $form->addErrorMessage($field->Name, $validationResult->message(), 'bad');
0 ignored issues
show
Bug introduced by
The method message() does not exist on SilverStripe\ORM\ValidationResult. Did you maybe mean addFieldMessage()?

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...
231
                            Controller::curr()->redirectBack();
232
                            return;
233
                        }
234
235
                        // write file to form field
236
                        $submittedField->UploadedFileID = $file->ID;
237
238
                        // attach a file only if lower than 1MB
239
                        if ($file->getAbsoluteSize() < 1024 * 1024 * 1) {
240
                            $attachments[] = $file;
241
                        }
242
                    }
243
                }
244
            }
245
246
            $submittedField->extend('onPopulationFromField', $field);
247
248
            if (!$this->DisableSaveSubmissions) {
249
                $submittedField->write();
250
            }
251
252
            $submittedFields->push($submittedField);
253
        }
254
255
        $emailData = [
256
            'Sender' => Security::getCurrentUser(),
257
            'Fields' => $submittedFields
258
        ];
259
260
        $this->extend('updateEmailData', $emailData, $attachments);
261
262
        // email users on submit.
263
        if ($recipients = $this->FilteredEmailRecipients($data, $form)) {
264
            foreach ($recipients as $recipient) {
265
                $email = Email::create()
266
                    ->setHTMLTemplate('email/SubmittedFormEmail.ss')
267
                    ->setPlainTemplate('email/SubmittedFormEmail.ss');
268
269
                $mergeFields = $this->getMergeFieldsMap($emailData['Fields']);
270
271
                if ($attachments) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attachments of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
272
                    foreach ($attachments as $file) {
273
                        if (!$file->ID != 0) {
274
                            continue;
275
                        }
276
277
                        $email->attachFile(
278
                            $file->Filename,
279
                            $file->Filename,
280
                            HTTP::get_mime_type($file->Filename)
281
                        );
282
                    }
283
                }
284
285
                $parsedBody = SSViewer::execute_string($recipient->getEmailBodyContent(), $mergeFields);
286
287
                if (!$recipient->SendPlain && $recipient->emailTemplateExists()) {
288
                    $email->setHTMLTemplate($recipient->EmailTemplate);
289
                }
290
291
                $email->setData($recipient);
292
                foreach ($emailData as $key => $value) {
293
                    $email->addData($key, $value);
294
                }
295
296
                $email->setFrom($recipient->EmailFrom);
297
                $email->setBody($parsedBody);
298
                $email->setTo($recipient->EmailAddress);
299
                $email->setSubject($recipient->EmailSubject);
300
301
                if ($recipient->EmailReplyTo) {
302
                    $email->setReplyTo($recipient->EmailReplyTo);
303
                }
304
305
                // check to see if they are a dynamic reply to. eg based on a email field a user selected
306 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...
307
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailFromField()->Name);
308
309
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
310
                        $email->setReplyTo($submittedFormField->Value);
311
                    }
312
                }
313
                // check to see if they are a dynamic reciever eg based on a dropdown field a user selected
314 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...
315
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailToField()->Name);
316
317
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
318
                        $email->setTo($submittedFormField->Value);
319
                    }
320
                }
321
322
                // check to see if there is a dynamic subject
323 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...
324
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailSubjectField()->Name);
325
326
                    if ($submittedFormField && trim($submittedFormField->Value)) {
327
                        $email->setSubject($submittedFormField->Value);
328
                    }
329
                }
330
331
                $this->extend('updateEmail', $email, $recipient, $emailData);
332
333
                if ($recipient->SendPlain) {
334
                    $body = strip_tags($recipient->getEmailBodyContent()) . "\n";
335
                    if (isset($emailData['Fields']) && !$recipient->HideFormData) {
336
                        foreach ($emailData['Fields'] as $Field) {
337
                            $body .= $Field->Title . ': ' . $Field->Value . " \n";
338
                        }
339
                    }
340
341
                    $email->setBody($body);
342
                    $email->sendPlain();
343
                } else {
344
                    $email->send();
345
                }
346
            }
347
        }
348
349
        $submittedForm->extend('updateAfterProcess');
350
351
        $session = $this->getRequest()->getSession();
352
        $session->clear("FormInfo.{$form->FormName()}.errors");
353
        $session->clear("FormInfo.{$form->FormName()}.data");
354
355
        $referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
356
357
        // set a session variable from the security ID to stop people accessing
358
        // the finished method directly.
359
        if (!$this->DisableAuthenicatedFinishAction) {
360
            if (isset($data['SecurityID'])) {
361
                $session->set('FormProcessed', $data['SecurityID']);
362
            } else {
363
                // if the form has had tokens disabled we still need to set FormProcessed
364
                // to allow us to get through the finshed method
365
                if (!$this->Form()->getSecurityToken()->isEnabled()) {
366
                    $randNum = rand(1, 1000);
367
                    $randHash = md5($randNum);
368
                    $session->set('FormProcessed', $randHash);
369
                    $session->set('FormProcessedNum', $randNum);
370
                }
371
            }
372
        }
373
374
        if (!$this->DisableSaveSubmissions) {
375
            $session->set('userformssubmission'. $this->ID, $submittedForm->ID);
376
        }
377
378
        return $this->redirect($this->Link('finished') . $referrer . $this->config()->get('finished_anchor'));
379
    }
380
381
    /**
382
     * Allows the use of field values in email body.
383
     *
384
     * @param ArrayList fields
385
     * @return ArrayData
386
     */
387
    protected function getMergeFieldsMap($fields = [])
388
    {
389
        $data = ArrayData::create([]);
390
391
        foreach ($fields as $field) {
392
            $data->setField($field->Name, DBField::create_field('Text', $field->Value));
393
        }
394
395
        return $data;
396
    }
397
398
    /**
399
     * This action handles rendering the "finished" message, which is
400
     * customizable by editing the ReceivedFormSubmission template.
401
     *
402
     * @return ViewableData
403
     */
404
    public function finished()
0 ignored issues
show
Coding Style introduced by
finished uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
405
    {
406
        $submission = $this->getRequest()->getSession()->get('userformssubmission'. $this->ID);
407
408
        if ($submission) {
409
            $submission = SubmittedForm::get()->byId($submission);
410
        }
411
412
        $referrer = isset($_GET['referrer']) ? urldecode($_GET['referrer']) : null;
413
414
        if (!$this->DisableAuthenicatedFinishAction) {
415
            $formProcessed = $this->getRequest()->getSession()->get('FormProcessed');
416
417
            if (!isset($formProcessed)) {
418
                return $this->redirect($this->Link() . $referrer);
419
            } else {
420
                $securityID = $this->getRequest()->getSession()->get('SecurityID');
421
                // make sure the session matches the SecurityID and is not left over from another form
422
                if ($formProcessed != $securityID) {
423
                    // they may have disabled tokens on the form
424
                    $securityID = md5($this->getRequest()->getSession()->get('FormProcessedNum'));
425
                    if ($formProcessed != $securityID) {
426
                        return $this->redirect($this->Link() . $referrer);
427
                    }
428
                }
429
            }
430
431
            $this->getRequest()->getSession()->clear('FormProcessed');
432
        }
433
434
        $data = [
435
            'Submission' => $submission,
436
            'Link' => $referrer
437
        ];
438
439
        $this->extend('updateReceivedFormSubmissionData', $data);
440
441
        return $this->customise([
442
            'Content' => $this->customise($data)->renderWith(__CLASS__ . '_ReceivedFormSubmission'),
443
            'Form' => '',
444
        ]);
445
    }
446
447
    /**
448
     * Outputs the required JS from the $watch input
449
     *
450
     * @param array $watch
451
     *
452
     * @return string
453
     */
454
    protected function buildWatchJS($watch)
455
    {
456
        $result = '';
457
        foreach ($watch as $key => $rule) {
458
            $events = implode(' ', $rule['events']);
459
            $selectors = implode(', ', $rule['selectors']);
460
            $conjunction = $rule['conjunction'];
461
            $operations = implode(" {$conjunction} ", $rule['operations']);
462
            $target = $rule['targetFieldID'];
463
464
            $result .= <<<EOS
465
\n
466
    $('.userform').on('{$events}',
467
    "{$selectors}",
468
    function (){
469
        if ({$operations}) {
470
            $('{$target}').{$rule['view']};
471
        } else {
472
            $('{$target}').{$rule['opposite']};
473
        }
474
    });
475
    $("{$target}").find('.hide').removeClass('hide');
476
EOS;
477
        }
478
479
        return $result;
480
    }
481
}
482