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

generateConditionalJavascript()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 31
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 8.439
c 0
b 0
f 0
cc 6
eloc 16
nc 8
nop 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\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
        // 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
Bug introduced by
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 = '';
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...
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) {
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...
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)
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...
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');
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...
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) {
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...
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()) {
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...
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()) {
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...
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()) {
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...
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()
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...
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