Completed
Push — master ( 064701...35dca0 )
by Daniel
9s
created

UserDefinedForm_Controller::process()   F

Complexity

Conditions 39
Paths 4108

Size

Total Lines 187
Code Lines 105

Duplication

Lines 21
Ratio 11.23 %

Code Coverage

Tests 90
CRAP Score 83.311

Importance

Changes 0
Metric Value
dl 21
loc 187
ccs 90
cts 130
cp 0.6923
rs 2
c 0
b 0
f 0
cc 39
eloc 105
nc 4108
nop 2
crap 83.311

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
/**
4
 * @package userforms
5
 */
6
7
class UserDefinedForm extends Page
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
8
{
9
    
10
    /**
11
     * @var string
12
     */
13
    private static $icon = 'userforms/images/sitetree_icon.png';
0 ignored issues
show
Unused Code introduced by
The property $icon 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...
14
15
    /**
16
     * @var string
17
     */
18
    private static $description = 'Adds a customizable form.';
0 ignored issues
show
Unused Code introduced by
The property $description 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...
19
20
    /**
21
     * @var string Required Identifier
22
     */
23
    private static $required_identifier = null;
0 ignored issues
show
Unused Code introduced by
The property $required_identifier 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...
24
25
    /**
26
     * @var string
27
     */
28
    private static $email_template_directory = 'userforms/templates/email/';
0 ignored issues
show
Unused Code introduced by
The property $email_template_directory 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...
29
30
    /**
31
     * Should this module automatically upgrade on dev/build?
32
     *
33
     * @config
34
     * @var bool
35
     */
36
    private static $upgrade_on_build = true;
0 ignored issues
show
Unused Code introduced by
The property $upgrade_on_build 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...
37
38
    /**
39
     * Built in extensions required by this page
40
     * @config
41
     * @var array
42
     */
43
    private static $extensions = array(
0 ignored issues
show
Unused Code introduced by
The property $extensions 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...
44
        'UserFormFieldEditorExtension'
45
    );
46
47
    /**
48
     * @var array Fields on the user defined form page.
49
     */
50
    private static $db = array(
0 ignored issues
show
Unused Code introduced by
The property $db 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...
51
        "SubmitButtonText" => "Varchar",
52
        "ClearButtonText" => "Varchar",
53
        "OnCompleteMessage" => "HTMLText",
54
        "ShowClearButton" => "Boolean",
55
        'DisableSaveSubmissions' => 'Boolean',
56
        'EnableLiveValidation' => 'Boolean',
57
        'HideFieldLabels' => 'Boolean',
58
        'DisplayErrorMessagesAtTop' => 'Boolean',
59
        'DisableAuthenicatedFinishAction' => 'Boolean',
60
        'DisableCsrfSecurityToken' => 'Boolean'
61
    );
62
63
    /**
64
     * @var array Default values of variables when this page is created
65
     */
66
    private static $defaults = array(
0 ignored issues
show
Unused Code introduced by
The property $defaults 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...
67
        'Content' => '$UserDefinedForm',
68
        'DisableSaveSubmissions' => 0,
69
        'OnCompleteMessage' => '<p>Thanks, we\'ve received your submission.</p>'
70
    );
71
72
    /**
73
     * @var array
74
     */
75
    private static $has_many = array(
0 ignored issues
show
Unused Code introduced by
The property $has_many 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...
76
        "Submissions" => "SubmittedForm",
77
        "EmailRecipients" => "UserDefinedForm_EmailRecipient"
78
    );
79
80
    /**
81
     * @var array
82
     * @config
83
     */
84
    private static $casting = array(
0 ignored issues
show
Unused Code introduced by
The property $casting 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...
85
        'ErrorContainerID' => 'Text'
86
    );
87
88
    /**
89
     * Error container selector which matches the element for grouped messages
90
     *
91
     * @var string
92
     * @config
93
     */
94
    private static $error_container_id = 'error-container';
0 ignored issues
show
Unused Code introduced by
The property $error_container_id 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...
95
96
    /**
97
     * The configuration used to determine whether a confirmation message is to
98
     * appear when navigating away from a partially completed form.
99
     *
100
     * @var boolean
101
     * @config
102
     */
103
    private static $enable_are_you_sure = true;
0 ignored issues
show
Unused Code introduced by
The property $enable_are_you_sure 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...
104
105
    /**
106
     * @var bool
107
     * @config
108
     */
109
    private static $recipients_warning_enabled = false;
0 ignored issues
show
Unused Code introduced by
The property $recipients_warning_enabled 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...
110
111
    /**
112
     * Temporary storage of field ids when the form is duplicated.
113
     * Example layout: array('EditableCheckbox3' => 'EditableCheckbox14')
114
     * @var array
115
     */
116
    protected $fieldsFromTo = array();
117
118
    /**
119
     * @return FieldList
120
     */
121 1
     public function getCMSFields()
122 1
     {
123 1
         Requirements::css(USERFORMS_DIR . '/css/UserForm_cms.css');
124
125 1
         $self = $this;
126
127
         $this->beforeUpdateCMSFields(function ($fields) use ($self) {
128
129
            // define tabs
130 1
            $fields->findOrMakeTab('Root.FormOptions', _t('UserDefinedForm.CONFIGURATION', 'Configuration'));
131 1
            $fields->findOrMakeTab('Root.Recipients', _t('UserDefinedForm.RECIPIENTS', 'Recipients'));
132 1
            $fields->findOrMakeTab('Root.Submissions', _t('UserDefinedForm.SUBMISSIONS', 'Submissions'));
133
134
            // text to show on complete
135 1
            $onCompleteFieldSet = new CompositeField(
136 1
                $label = new LabelField('OnCompleteMessageLabel', _t('UserDefinedForm.ONCOMPLETELABEL', 'Show on completion')),
137 1
                $editor = new HtmlEditorField('OnCompleteMessage', '', _t('UserDefinedForm.ONCOMPLETEMESSAGE', $self->OnCompleteMessage))
138 1
            );
139
140 1
            $onCompleteFieldSet->addExtraClass('field');
141
142 1
            $editor->setRows(3);
143 1
            $label->addExtraClass('left');
144
145
            // Define config for email recipients
146 1
            $emailRecipientsConfig = GridFieldConfig_RecordEditor::create(10);
147 1
            $emailRecipientsConfig->getComponentByType('GridFieldAddNewButton')
148 1
                ->setButtonName(
149 1
                    _t('UserDefinedForm.ADDEMAILRECIPIENT', 'Add Email Recipient')
150 1
                );
151
152
            // who do we email on submission
153 1
            $emailRecipients = new GridField(
154 1
                'EmailRecipients',
155 1
                _t('UserDefinedForm.EMAILRECIPIENTS', 'Email Recipients'),
156 1
                $self->EmailRecipients(),
157
                $emailRecipientsConfig
158 1
            );
159
            $emailRecipients
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface GridFieldComponent as the method setItemRequestClass() does only exist in the following implementations of said interface: GridFieldAddNewMultiClass, GridFieldDetailForm.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
160 1
                ->getConfig()
161 1
                ->getComponentByType('GridFieldDetailForm')
162 1
                ->setItemRequestClass('UserFormRecipientItemRequest');
163
164 1
            $fields->addFieldsToTab('Root.FormOptions', $onCompleteFieldSet);
165 1
            $fields->addFieldToTab('Root.Recipients', $emailRecipients);
166 1
            $fields->addFieldsToTab('Root.FormOptions', $self->getFormOptions());
167
168
169
            // view the submissions
170 1
            $submissions = new GridField(
171 1
                'Submissions',
172 1
                _t('UserDefinedForm.SUBMISSIONS', 'Submissions'),
173 1
                 $self->Submissions()->sort('Created', 'DESC')
174 1
            );
175
176
            // make sure a numeric not a empty string is checked against this int column for SQL server
177 1
            $parentID = (!empty($self->ID)) ? (int) $self->ID : 0;
178
179
            // get a list of all field names and values used for print and export CSV views of the GridField below.
180
            $columnSQL = <<<SQL
181
SELECT "SubmittedFormField"."Name" as "Name", COALESCE("EditableFormField"."Title", "SubmittedFormField"."Title") as "Title", COALESCE("EditableFormField"."Sort", 999) AS "Sort"
182
FROM "SubmittedFormField"
183
LEFT JOIN "SubmittedForm" ON "SubmittedForm"."ID" = "SubmittedFormField"."ParentID"
184
LEFT JOIN "EditableFormField" ON "EditableFormField"."Name" = "SubmittedFormField"."Name" AND "EditableFormField"."ParentID" = '$parentID'
185 1
WHERE "SubmittedForm"."ParentID" = '$parentID'
186 1
ORDER BY "Sort", "Title"
187 1
SQL;
188
            // Sanitise periods in title
189 1
            $columns = array();
190 1
            foreach (DB::query($columnSQL)->map() as $name => $title) {
191
                $columns[$name] = trim(strtr($title, '.', ' '));
192 1
            }
193
194 1
            $config = new GridFieldConfig();
195 1
            $config->addComponent(new GridFieldToolbarHeader());
196 1
            $config->addComponent($sort = new GridFieldSortableHeader());
197 1
            $config->addComponent($filter = new UserFormsGridFieldFilterHeader());
198 1
            $config->addComponent(new GridFieldDataColumns());
199 1
            $config->addComponent(new GridFieldEditButton());
200 1
            $config->addComponent(new GridFieldDeleteAction());
201 1
            $config->addComponent(new GridFieldPageCount('toolbar-header-right'));
202 1
            $config->addComponent($pagination = new GridFieldPaginator(25));
203 1
            $config->addComponent(new GridFieldDetailForm());
204 1
            $config->addComponent(new GridFieldButtonRow('after'));
205 1
            $config->addComponent($export = new GridFieldExportButton('buttons-after-left'));
206 1
            $config->addComponent($print = new GridFieldPrintButton('buttons-after-left'));
207
208
            /**
209
             * Support for {@link https://github.com/colymba/GridFieldBulkEditingTools}
210
             */
211 1
            if (class_exists('GridFieldBulkManager')) {
212
                $config->addComponent(new GridFieldBulkManager());
213
            }
214
215 1
            $sort->setThrowExceptionOnBadDataType(false);
216 1
            $filter->setThrowExceptionOnBadDataType(false);
217 1
            $pagination->setThrowExceptionOnBadDataType(false);
218
219
            // attach every column to the print view form
220 1
            $columns['Created'] = 'Created';
221 1
            $filter->setColumns($columns);
222
223
            // print configuration
224
225 1
            $print->setPrintHasHeader(true);
226 1
            $print->setPrintColumns($columns);
227
228
            // export configuration
229 1
            $export->setCsvHasHeader(true);
230 1
            $export->setExportColumns($columns);
231
232 1
            $submissions->setConfig($config);
233 1
            $fields->addFieldToTab('Root.Submissions', $submissions);
234 1
            $fields->addFieldToTab('Root.FormOptions', new CheckboxField('DisableSaveSubmissions', _t('UserDefinedForm.SAVESUBMISSIONS', 'Disable Saving Submissions to Server')));
235
236 1
        });
237
238 1
         $fields = parent::getCMSFields();
239
240 1
         if ($this->EmailRecipients()->Count() == 0 && static::config()->recipients_warning_enabled) {
241
             $fields->addFieldToTab("Root.Main", new LiteralField("EmailRecipientsWarning",
242
                "<p class=\"message warning\">" . _t("UserDefinedForm.NORECIPIENTS",
243
                "Warning: You have not configured any recipients. Form submissions may be missed.")
244
                . "</p>"), "Title");
245
         }
246
247 1
         return $fields;
248
     }
249
250
    /**
251
     * Allow overriding the EmailRecipients on a {@link DataExtension}
252
     * so you can customise who receives an email.
253
     * Converts the RelationList to an ArrayList so that manipulation
254
     * of the original source data isn't possible.
255
     *
256
     * @return ArrayList
257
     */
258 3
    public function FilteredEmailRecipients($data = null, $form = null)
259
    {
260 3
        $recipients = new ArrayList($this->EmailRecipients()->toArray());
261
262
        // Filter by rules
263
        $recipients = $recipients->filterByCallback(function ($recipient) use ($data, $form) {
264 2
            return $recipient->canSend($data, $form);
265 3
        });
266
267 3
        $this->extend('updateFilteredEmailRecipients', $recipients, $data, $form);
268
269 3
        return $recipients;
270
    }
271
272
    /**
273
     * Custom options for the form. You can extend the built in options by
274
     * using {@link updateFormOptions()}
275
     *
276
     * @return FieldList
277
     */
278 2
    public function getFormOptions()
279
    {
280 2
        $submit = ($this->SubmitButtonText) ? $this->SubmitButtonText : _t('UserDefinedForm.SUBMITBUTTON', 'Submit');
281 2
        $clear = ($this->ClearButtonText) ? $this->ClearButtonText : _t('UserDefinedForm.CLEARBUTTON', 'Clear');
282
283 2
        $options = new FieldList(
284 2
            new TextField("SubmitButtonText", _t('UserDefinedForm.TEXTONSUBMIT', 'Text on submit button:'), $submit),
285 2
            new TextField("ClearButtonText", _t('UserDefinedForm.TEXTONCLEAR', 'Text on clear button:'), $clear),
286 2
            new CheckboxField("ShowClearButton", _t('UserDefinedForm.SHOWCLEARFORM', 'Show Clear Form Button'), $this->ShowClearButton),
287 2
            new CheckboxField("EnableLiveValidation", _t('UserDefinedForm.ENABLELIVEVALIDATION', 'Enable live validation')),
288 2
            new CheckboxField("HideFieldLabels", _t('UserDefinedForm.HIDEFIELDLABELS', 'Hide field labels')),
289 2
            new CheckboxField("DisplayErrorMessagesAtTop", _t('UserDefinedForm.DISPLAYERRORMESSAGESATTOP', 'Display error messages above the form?')),
290 2
            new CheckboxField('DisableCsrfSecurityToken', _t('UserDefinedForm.DISABLECSRFSECURITYTOKEN', 'Disable CSRF Token')),
291 2
            new CheckboxField('DisableAuthenicatedFinishAction', _t('UserDefinedForm.DISABLEAUTHENICATEDFINISHACTION', 'Disable Authentication on finish action'))
292 2
        );
293
294 2
        $this->extend('updateFormOptions', $options);
295
296 2
        return $options;
297
    }
298
299
    /**
300
     * Get the HTML id of the error container displayed above the form.
301
     *
302
     * @return string
303
     */
304
    public function getErrorContainerID()
305
    {
306
        return $this->config()->error_container_id;
307
    }
308
309
    public function requireDefaultRecords()
310
    {
311
        parent::requireDefaultRecords();
312
313
        if (!$this->config()->upgrade_on_build) {
314
            return;
315
        }
316
317
        // Perform migrations
318
        Injector::inst()
319
            ->create('UserFormsUpgradeService')
320
            ->setQuiet(true)
321
            ->run();
322
323
        DB::alteration_message('Migrated userforms', 'changed');
324
    }
325
326
327
    /**
328
     * Validate formfields
329
     */
330
    public function getCMSValidator()
331
    {
332
        return new UserFormValidator();
333
    }
334
}
335
336
/**
337
 * Controller for the {@link UserDefinedForm} page type.
338
 *
339
 * @package userforms
340
 */
341
342
class UserDefinedForm_Controller extends Page_Controller
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
343
{
344
345
    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...
346
347
    private static $allowed_actions = array(
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...
348
        'index',
349
        'ping',
350
        'Form',
351
        'finished'
352
    );
353
354 5
    public function init()
355
    {
356 5
        parent::init();
357
358
        // load the jquery
359 5
        $lang = i18n::get_lang_from_locale(i18n::get_locale());
360 5
        Requirements::css(USERFORMS_DIR . '/css/UserForm.css');
361 5
        Requirements::javascript(FRAMEWORK_DIR .'/thirdparty/jquery/jquery.js');
362 5
        Requirements::javascript(USERFORMS_DIR . '/thirdparty/jquery-validate/jquery.validate.min.js');
363 5
        Requirements::add_i18n_javascript(USERFORMS_DIR . '/javascript/lang');
364 5
        Requirements::javascript(USERFORMS_DIR . '/javascript/UserForm.js');
365
366 5
        Requirements::javascript(
367 5
            USERFORMS_DIR . "/thirdparty/jquery-validate/localization/messages_{$lang}.min.js"
368 5
        );
369 5
        Requirements::javascript(
370 5
            USERFORMS_DIR . "/thirdparty/jquery-validate/localization/methods_{$lang}.min.js"
371 5
        );
372 5
        if ($this->HideFieldLabels) {
373
            Requirements::javascript(USERFORMS_DIR . '/thirdparty/Placeholders.js/Placeholders.min.js');
374
        }
375
376
        // Bind a confirmation message when navigating away from a partially completed form.
377 5
        $page = $this->data();
378 5
        if ($page::config()->enable_are_you_sure) {
379 5
            Requirements::javascript(USERFORMS_DIR . '/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js');
380 5
        }
381 5
    }
382
383
    /**
384
     * Using $UserDefinedForm in the Content area of the page shows
385
     * where the form should be rendered into. If it does not exist
386
     * then default back to $Form.
387
     *
388
     * @return array
389
     */
390 6
    public function index()
391
    {
392 6
        if ($this->Content && $form = $this->Form()) {
393 6
            $hasLocation = stristr($this->Content, '$UserDefinedForm');
394 6
            if ($hasLocation) {
395 5
                $content = preg_replace('/(<p[^>]*>)?\\$UserDefinedForm(<\\/p>)?/i', $form->forTemplate(), $this->Content);
396
                return array(
397 5
                    'Content' => DBField::create_field('HTMLText', $content),
398
                    'Form' => ""
399 5
                );
400
            }
401 1
        }
402
403
        return array(
404 1
            'Content' => DBField::create_field('HTMLText', $this->Content),
405 1
            'Form' => $this->Form()
406 1
        );
407
    }
408
409
    /**
410
     * Keep the session alive for the user.
411
     *
412
     * @return int
413
     */
414
    public function ping()
415
    {
416
        return 1;
417
    }
418
419
    /**
420
     * Get the form for the page. Form can be modified by calling {@link updateForm()}
421
     * on a UserDefinedForm extension.
422
     *
423
     * @return Forms
424
     */
425 9
    public function Form()
426
    {
427 9
        $form = UserForm::create($this);
428 9
        $this->generateConditionalJavascript();
429 9
        return $form;
430
    }
431
432
    /**
433
     * Generate the javascript for the conditional field show / hiding logic.
434
     *
435
     * @return void
436
     */
437 9
    public function generateConditionalJavascript()
438
    {
439 9
        $default = "";
440 9
        $rules = "";
441
442 9
        $watch = array();
443 9
        $watchLoad = array();
444
445 9
        if ($this->Fields()) {
446 9
            foreach ($this->Fields() as $field) {
447 8
                $holderSelector = $field->getSelectorHolder();
448
449
                // Is this Field Show by Default
450 8
                if (!$field->ShowOnLoad) {
451
                    $default .= "{$holderSelector}.hide().trigger('userform.field.hide');\n";
452
                }
453
454
                // Check for field dependencies / default
455 8
                foreach ($field->EffectiveDisplayRules() as $rule) {
456
457
                    // Get the field which is effected
458
                    $formFieldWatch = EditableFormField::get()->byId($rule->ConditionFieldID);
459
460
                    // Skip deleted fields
461
                    if (!$formFieldWatch) {
462
                        continue;
463
                    }
464
465
                    $fieldToWatch = $formFieldWatch->getSelectorField($rule);
466
                    $fieldToWatchOnLoad = $formFieldWatch->getSelectorField($rule, true);
467
468
                    // show or hide?
469
                    $view = ($rule->Display == 'Hide') ? 'hide' : 'show';
470
                    $opposite = ($view == "show") ? "hide" : "show";
471
472
                    // what action do we need to keep track of. Something nicer here maybe?
473
                    // @todo encapulsation
474
                    $action = "change";
475
476
                    if ($formFieldWatch instanceof EditableTextField) {
477
                        $action = "keyup";
478
                    }
479
480
                    // is this field a special option field
481
                    $checkboxField = false;
482
                    $radioField = false;
483
484
                    if (in_array($formFieldWatch->ClassName, array('EditableCheckboxGroupField', 'EditableCheckbox'))) {
485
                        $action = "click";
486
                        $checkboxField = true;
487
                    } elseif ($formFieldWatch->ClassName == "EditableRadioField") {
488
                        $radioField = true;
489
                    }
490
491
                    // and what should we evaluate
492
                    switch ($rule->ConditionOption) {
493
                        case 'IsNotBlank':
494
                            $expression = ($checkboxField || $radioField) ? '$(this).is(":checked")' :'$(this).val() != ""';
495
496
                            break;
497
                        case 'IsBlank':
498
                            $expression = ($checkboxField || $radioField) ? '!($(this).is(":checked"))' : '$(this).val() == ""';
499
500
                            break;
501 View Code Duplication
                        case 'HasValue':
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...
502
                            if ($checkboxField) {
503
                                $expression = '$(this).prop("checked")';
504
                            } elseif ($radioField) {
505
                                // We cannot simply get the value of the radio group, we need to find the checked option first.
506
                                $expression = '$(this).parents(".field, .control-group").find("input:checked").val()=="'. $rule->FieldValue .'"';
507
                            } else {
508
                                $expression = '$(this).val() == "'. $rule->FieldValue .'"';
509
                            }
510
511
                            break;
512
                        case 'ValueLessThan':
513
                            $expression = '$(this).val() < parseFloat("'. $rule->FieldValue .'")';
514
515
                            break;
516
                        case 'ValueLessThanEqual':
517
                            $expression = '$(this).val() <= parseFloat("'. $rule->FieldValue .'")';
518
519
                            break;
520
                        case 'ValueGreaterThan':
521
                            $expression = '$(this).val() > parseFloat("'. $rule->FieldValue .'")';
522
523
                            break;
524
                        case 'ValueGreaterThanEqual':
525
                            $expression = '$(this).val() >= parseFloat("'. $rule->FieldValue .'")';
526
527
                            break;
528 View Code Duplication
                        default: // ==HasNotValue
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...
529
                            if ($checkboxField) {
530
                                $expression = '!$(this).prop("checked")';
531
                            } elseif ($radioField) {
532
                                // We cannot simply get the value of the radio group, we need to find the checked option first.
533
                                $expression = '$(this).parents(".field, .control-group").find("input:checked").val()!="'. $rule->FieldValue .'"';
534
                            } else {
535 1
                                $expression = '$(this).val() != "'. $rule->FieldValue .'"';
536
                            }
537
538
                            break;
539
                    }
540
541
                    if (!isset($watch[$fieldToWatch])) {
542
                        $watch[$fieldToWatch] = array();
543
                    }
544
545
                    $watch[$fieldToWatch][] = array(
546
                        'expression' => $expression,
547
                        'holder_selector' => $holderSelector,
548 1
                        'view' => $view,
549
                        'opposite' => $opposite,
550
                        'action' => $action
551
                    );
552
553
                    $watchLoad[$fieldToWatchOnLoad] = true;
554 8
                }
555 9
            }
556 9
        }
557
558 9
        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...
559
            foreach ($watch as $key => $values) {
560
                $logic = array();
561
                $actions = array();
562
563
                foreach ($values as $rule) {
564
                    // Assign action
565
                    $actions[$rule['action']] = $rule['action'];
566
567
                    // Assign behaviour
568
                    $expression = $rule['expression'];
569
                    $holder = $rule['holder_selector'];
570
                    $view = $rule['view']; // hide or show
571
                    $opposite = $rule['opposite'];
572
                    // Generated javascript for triggering visibility
573
                    $logic[] = <<<"EOS"
574
if({$expression}) {
575
	{$holder}
576
		.{$view}()
577
		.trigger('userform.field.{$view}');
578
} else {
579
	{$holder}
580
		.{$opposite}()
581
		.trigger('userform.field.{$opposite}');
582
}
583
EOS;
584
                }
585
586
                $logic = implode("\n", $logic);
587
                $rules .= $key.".each(function() {\n
588
	$(this).data('userformConditions', function() {\n
589
		$logic\n
590
	}); \n
591
});\n";
592
                foreach ($actions as $action) {
593
                    $rules .= $key.".$action(function() {
594
	$(this).data('userformConditions').call(this);\n
595
});\n";
596
                }
597
            }
598
        }
599
600 9
        if ($watchLoad) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $watchLoad 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...
601 1
            foreach ($watchLoad as $key => $value) {
602
                $rules .= $key.".each(function() {
603
	$(this).data('userformConditions').call(this);\n
604
});\n";
605
            }
606
        }
607
608
        // Only add customScript if $default or $rules is defined
609 9
        if ($default  || $rules) {
610
            Requirements::customScript(<<<JS
611
				(function($) {
612
					$(document).ready(function() {
613
						$default
614
615
						$rules
616 1
					})
617
				})(jQuery);
618
JS
619
, 'UserFormsConditional');
620
        }
621 9
    }
622
623
    /**
624
     * Process the form that is submitted through the site
625
     *
626
     * {@see UserForm::validate()} for validation step prior to processing
627
     *
628
     * @param array $data
629
     * @param Form $form
630
     *
631
     * @return Redirection
632
     */
633 3
    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...
634
    {
635 2
        $submittedForm = Object::create('SubmittedForm');
636 2
        $submittedForm->SubmittedByID = ($id = Member::currentUserID()) ? $id : 0;
637 2
        $submittedForm->ParentID = $this->ID;
638
639
        // if saving is not disabled save now to generate the ID
640 2
        if (!$this->DisableSaveSubmissions) {
641 2
            $submittedForm->write();
642 2
        }
643
644 2
        $attachments = array();
645 2
        $submittedFields = new ArrayList();
646
647 2
        foreach ($this->Fields() as $field) {
648 2
            if (!$field->showInReports()) {
649 2
                continue;
650
            }
651
652 2
            $submittedField = $field->getSubmittedFormField();
653 2
            $submittedField->ParentID = $submittedForm->ID;
654 2
            $submittedField->Name = $field->Name;
655 2
            $submittedField->Title = $field->getField('Title');
656
657
            // save the value from the data
658 2
            if ($field->hasMethod('getValueFromData')) {
659
                $submittedField->Value = $field->getValueFromData($data);
660
            } else {
661 2
                if (isset($data[$field->Name])) {
662 2
                    $submittedField->Value = $data[$field->Name];
663 2
                }
664
            }
665
666 2
            if (!empty($data[$field->Name])) {
667 2
                if (in_array("EditableFileField", $field->getClassAncestry())) {
668
                    if (isset($_FILES[$field->Name])) {
669 3
                        $foldername = $field->getFormField()->getFolderName();
670
671
                        // create the file from post data
672
                        $upload = new Upload();
673
                        $file = new File();
674
                        $file->ShowInSearch = 0;
675
                        try {
676
                            $upload->loadIntoFile($_FILES[$field->Name], $file, $foldername);
677
                        } catch (ValidationException $e) {
678
                            $validationResult = $e->getResult();
679
                            $form->addErrorMessage($field->Name, $validationResult->message(), 'bad');
680
                            Controller::curr()->redirectBack();
681
                            return;
682
                        }
683
684
                        // write file to form field
685
                        $submittedField->UploadedFileID = $file->ID;
686
687
                        // attach a file only if lower than 1MB
688
                        if ($file->getAbsoluteSize() < 1024*1024*1) {
689
                            $attachments[] = $file;
690
                        }
691
                    }
692
                }
693 2
            }
694
695 2
            $submittedField->extend('onPopulationFromField', $field);
696
697 2
            if (!$this->DisableSaveSubmissions) {
698 2
                $submittedField->write();
699 2
            }
700
701 2
            $submittedFields->push($submittedField);
702 2
        }
703
704
        $emailData = array(
705 2
            "Sender" => Member::currentUser(),
706
            "Fields" => $submittedFields
707 2
        );
708
709 2
        $this->extend('updateEmailData', $emailData, $attachments);
710
711
        // email users on submit.
712 2
        if ($recipients = $this->FilteredEmailRecipients($data, $form)) {
713 2
            foreach ($recipients as $recipient) {
714 1
                $email = new UserFormRecipientEmail($submittedFields);
715 1
                $mergeFields = $this->getMergeFieldsMap($emailData['Fields']);
716
    
717 1
                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...
718
                    foreach ($attachments as $file) {
719
                        if ($file->ID != 0) {
720
                            $email->attachFile(
721
                                $file->Filename,
722
                                $file->Filename,
723
                                HTTP::get_mime_type($file->Filename)
724
                            );
725
                        }
726
                    }
727
                }
728
                
729 1
                $parsedBody = SSViewer::execute_string($recipient->getEmailBodyContent(), $mergeFields);
730
731 1
                if (!$recipient->SendPlain && $recipient->emailTemplateExists()) {
732
                    $email->setTemplate($recipient->EmailTemplate);
733
                }
734
735 1
                $email->populateTemplate($recipient);
736 1
                $email->populateTemplate($emailData);
737 1
                $email->setFrom($recipient->EmailFrom);
738 1
                $email->setBody($parsedBody);
739 1
                $email->setTo($recipient->EmailAddress);
740 1
                $email->setSubject($recipient->EmailSubject);
741
742 1
                if ($recipient->EmailReplyTo) {
743
                    $email->setReplyTo($recipient->EmailReplyTo);
744
                }
745
746
                // check to see if they are a dynamic reply to. eg based on a email field a user selected
747 1 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...
748 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailFromField()->Name);
749
750 1
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
751
                        $email->setReplyTo($submittedFormField->Value);
752
                    }
753 1
                }
754
                // check to see if they are a dynamic reciever eg based on a dropdown field a user selected
755 1 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...
756 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailToField()->Name);
757
758 1
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
759
                        $email->setTo($submittedFormField->Value);
760
                    }
761 1
                }
762
763
                // check to see if there is a dynamic subject
764 1 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...
765 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailSubjectField()->Name);
766
767 1
                    if ($submittedFormField && trim($submittedFormField->Value)) {
768
                        $email->setSubject($submittedFormField->Value);
769
                    }
770 1
                }
771
772 1
                $this->extend('updateEmail', $email, $recipient, $emailData);
773
774 1
                if ($recipient->SendPlain) {
775 1
                    $body = strip_tags($recipient->getEmailBodyContent()) . "\n";
776 1
                    if (isset($emailData['Fields']) && !$recipient->HideFormData) {
777 1
                        foreach ($emailData['Fields'] as $Field) {
778 1
                            $body .= $Field->Title .': '. $Field->Value ." \n";
779 1
                        }
780 1
                    }
781
782 1
                    $email->setBody($body);
783 1
                    $email->sendPlain();
784 1
                } else {
785 1
                    $email->send();
786
                }
787 2
            }
788 2
        }
789
790 2
        $submittedForm->extend('updateAfterProcess');
791
792 2
        Session::clear("FormInfo.{$form->FormName()}.errors");
793 2
        Session::clear("FormInfo.{$form->FormName()}.data");
794
795 2
        $referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
796
797
        // set a session variable from the security ID to stop people accessing
798
        // the finished method directly.
799 2
        if (!$this->DisableAuthenicatedFinishAction) {
800 2
            if (isset($data['SecurityID'])) {
801
                Session::set('FormProcessed', $data['SecurityID']);
802
            } else {
803
                // if the form has had tokens disabled we still need to set FormProcessed
804
                // to allow us to get through the finshed method
805 2
                if (!$this->Form()->getSecurityToken()->isEnabled()) {
806 2
                    $randNum = rand(1, 1000);
807 2
                    $randHash = md5($randNum);
808 2
                    Session::set('FormProcessed', $randHash);
809 2
                    Session::set('FormProcessedNum', $randNum);
810 2
                }
811
            }
812 2
        }
813
814 2
        if (!$this->DisableSaveSubmissions) {
815 2
            Session::set('userformssubmission'. $this->ID, $submittedForm->ID);
816 2
        }
817
818 2
        return $this->redirect($this->Link('finished') . $referrer . $this->config()->finished_anchor);
819
    }
820
821
    /**
822
     * Allows the use of field values in email body.
823
     *
824
     * @param ArrayList fields
825
     * @return ArrayData
826
     */
827 1
    private function getMergeFieldsMap($fields = array())
828
    {
829 1
        $data = new ArrayData(array());
830
831 1
        foreach ($fields as $field) {
832 1
            $data->setField($field->Name, DBField::create_field('Text', $field->Value));
833 1
        }
834
835 1
        return $data;
836
    }
837
838
    /**
839
     * This action handles rendering the "finished" message, which is
840
     * customizable by editing the ReceivedFormSubmission template.
841
     *
842
     * @return ViewableData
843
     */
844 3
    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...
845
    {
846 3
        $submission = Session::get('userformssubmission'. $this->ID);
847
848 3
        if ($submission) {
849 1
            $submission = SubmittedForm::get()->byId($submission);
850 1
        }
851
852 3
        $referrer = isset($_GET['referrer']) ? urldecode($_GET['referrer']) : null;
853
854 3
        if (!$this->DisableAuthenicatedFinishAction) {
855 3
            $formProcessed = Session::get('FormProcessed');
856
857 3
            if (!isset($formProcessed)) {
858 1
                return $this->redirect($this->Link() . $referrer);
859
            } else {
860 2
                $securityID = Session::get('SecurityID');
861
                // make sure the session matches the SecurityID and is not left over from another form
862 2
                if ($formProcessed != $securityID) {
863
                    // they may have disabled tokens on the form
864 1
                    $securityID = md5(Session::get('FormProcessedNum'));
865 1
                    if ($formProcessed != $securityID) {
866
                        return $this->redirect($this->Link() . $referrer);
867
                    }
868 1
                }
869
            }
870
871 2
            Session::clear('FormProcessed');
872 2
        }
873
874 2
        return $this->customise(array(
875 2
            'Content' => $this->customise(array(
876 2
                'Submission' => $submission,
877
                'Link' => $referrer
878 2
            ))->renderWith('ReceivedFormSubmission'),
879 2
            'Form' => '',
880 2
        ));
881
    }
882
}
883