Completed
Push — master ( 67ead9...61a703 )
by Daniel
12s
created

UserDefinedForm_Controller   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 431
Duplicated Lines 4.87 %

Coupling/Cohesion

Components 1
Dependencies 20

Test Coverage

Coverage 73.68%

Importance

Changes 0
Metric Value
wmc 66
lcom 1
cbo 20
dl 21
loc 431
ccs 140
cts 190
cp 0.7368
rs 2.1568
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A ping() 0 4 1
A Form() 0 7 1
B init() 0 32 4
A index() 0 18 4
B generateConditionalJavascript() 0 31 6
F process() 21 187 39
A getMergeFieldsMap() 0 10 2
C finished() 0 42 7
B buildWatchJS() 0 27 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like UserDefinedForm_Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use UserDefinedForm_Controller, and based on these observations, apply Extract Interface, too.

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
     * Set this to true to disable automatic inclusion of CSS files
40
     * @config
41
     * @var bool
42
     */
43
    private static $block_default_userforms_css = false;
0 ignored issues
show
Unused Code introduced by
The property $block_default_userforms_css 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
45
    /**
46
     * Set this to true to disable automatic inclusion of JavaScript files
47
     * @config
48
     * @var bool
49
     */
50
    private static $block_default_userforms_js = false;
0 ignored issues
show
Unused Code introduced by
The property $block_default_userforms_js 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
52
    /**
53
     * Built in extensions required by this page
54
     * @config
55
     * @var array
56
     */
57
    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...
58
        'UserFormFieldEditorExtension'
59
    );
60
61
    /**
62
     * @var array Fields on the user defined form page.
63
     */
64
    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...
65
        "SubmitButtonText" => "Varchar",
66
        "ClearButtonText" => "Varchar",
67
        "OnCompleteMessage" => "HTMLText",
68
        "ShowClearButton" => "Boolean",
69
        'DisableSaveSubmissions' => 'Boolean',
70
        'EnableLiveValidation' => 'Boolean',
71
        'DisplayErrorMessagesAtTop' => 'Boolean',
72
        'DisableAuthenicatedFinishAction' => 'Boolean',
73
        'DisableCsrfSecurityToken' => 'Boolean'
74
    );
75
76
    /**
77
     * @var array Default values of variables when this page is created
78
     */
79
    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...
80
        'Content' => '$UserDefinedForm',
81
        'DisableSaveSubmissions' => 0,
82
        'OnCompleteMessage' => '<p>Thanks, we\'ve received your submission.</p>'
83
    );
84
85
    /**
86
     * @var array
87
     */
88
    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...
89
        "Submissions" => "SubmittedForm",
90
        "EmailRecipients" => "UserDefinedForm_EmailRecipient"
91
    );
92
93
    /**
94
     * @var array
95
     * @config
96
     */
97
    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...
98
        'ErrorContainerID' => 'Text'
99
    );
100
101
    /**
102
     * Error container selector which matches the element for grouped messages
103
     *
104
     * @var string
105
     * @config
106
     */
107
    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...
108
109
    /**
110
     * The configuration used to determine whether a confirmation message is to
111
     * appear when navigating away from a partially completed form.
112
     *
113
     * @var boolean
114
     * @config
115
     */
116
    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...
117
118
    /**
119
     * @var bool
120
     * @config
121
     */
122
    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...
123
124
    /**
125
     * Temporary storage of field ids when the form is duplicated.
126
     * Example layout: array('EditableCheckbox3' => 'EditableCheckbox14')
127
     * @var array
128
     */
129
    protected $fieldsFromTo = array();
130
131
    /**
132
     * @return FieldList
133
     */
134 2
    public function getCMSFields()
135
    {
136 2
        Requirements::css(USERFORMS_DIR . '/css/UserForm_cms.css');
137
138 2
        $self = $this;
139
140
        $this->beforeUpdateCMSFields(function ($fields) use ($self) {
141
            // define tabs
142 2
            $fields->findOrMakeTab('Root.FormOptions', _t('UserDefinedForm.CONFIGURATION', 'Configuration'));
143 2
            $fields->findOrMakeTab('Root.Recipients', _t('UserDefinedForm.RECIPIENTS', 'Recipients'));
144 2
            $fields->findOrMakeTab('Root.Submissions', _t('UserDefinedForm.SUBMISSIONS', 'Submissions'));
145
146
            // text to show on complete
147 2
            $onCompleteFieldSet = new CompositeField(
148 2
                $label = new LabelField('OnCompleteMessageLabel', _t('UserDefinedForm.ONCOMPLETELABEL', 'Show on completion')),
149 2
                $editor = new HtmlEditorField('OnCompleteMessage', '', _t('UserDefinedForm.ONCOMPLETEMESSAGE', $self->OnCompleteMessage))
150
            );
151
152 2
            $onCompleteFieldSet->addExtraClass('field');
153
154 2
            $editor->setRows(3);
155 2
            $label->addExtraClass('left');
156
157
            // Define config for email recipients
158 2
            $emailRecipientsConfig = GridFieldConfig_RecordEditor::create(10);
159 2
            $emailRecipientsConfig->getComponentByType('GridFieldAddNewButton')
160 2
                ->setButtonName(
161 2
                    _t('UserDefinedForm.ADDEMAILRECIPIENT', 'Add Email Recipient')
162
                );
163
164
            // who do we email on submission
165 2
            $emailRecipients = new GridField(
166 2
                'EmailRecipients',
167 2
                _t('UserDefinedForm.EMAILRECIPIENTS', 'Email Recipients'),
168 2
                $self->EmailRecipients(),
169 2
                $emailRecipientsConfig
170
            );
171
            $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...
172 2
                ->getConfig()
173 2
                ->getComponentByType('GridFieldDetailForm')
174 2
                ->setItemRequestClass('UserFormRecipientItemRequest');
175
176 2
            $fields->addFieldsToTab('Root.FormOptions', $onCompleteFieldSet);
177 2
            $fields->addFieldToTab('Root.Recipients', $emailRecipients);
178 2
            $fields->addFieldsToTab('Root.FormOptions', $self->getFormOptions());
179
180
181
            // view the submissions
182
            // make sure a numeric not a empty string is checked against this int column for SQL server
183 2
            $parentID = (!empty($self->ID)) ? (int) $self->ID : 0;
184
185
            // get a list of all field names and values used for print and export CSV views of the GridField below.
186
            $columnSQL = <<<SQL
187
SELECT "SubmittedFormField"."Name" as "Name", COALESCE("EditableFormField"."Title", "SubmittedFormField"."Title") as "Title", COALESCE("EditableFormField"."Sort", 999) AS "Sort"
188
FROM "SubmittedFormField"
189
LEFT JOIN "SubmittedForm" ON "SubmittedForm"."ID" = "SubmittedFormField"."ParentID"
190 2
LEFT JOIN "EditableFormField" ON "EditableFormField"."Name" = "SubmittedFormField"."Name" AND "EditableFormField"."ParentID" = '$parentID'
191 2
WHERE "SubmittedForm"."ParentID" = '$parentID'
192
ORDER BY "Sort", "Title"
193
SQL;
194
            // Sanitise periods in title
195 2
            $columns = array();
196 2
            foreach (DB::query($columnSQL)->map() as $name => $title) {
197
                $columns[$name] = trim(strtr($title, '.', ' '));
198
            }
199
200 2
            $config = new GridFieldConfig();
201 2
            $config->addComponent(new GridFieldToolbarHeader());
202 2
            $config->addComponent($sort = new GridFieldSortableHeader());
203 2
            $config->addComponent($filter = new UserFormsGridFieldFilterHeader());
204 2
            $config->addComponent(new GridFieldDataColumns());
205 2
            $config->addComponent(new GridFieldEditButton());
206 2
            $config->addComponent(new GridFieldDeleteAction());
207 2
            $config->addComponent(new GridFieldPageCount('toolbar-header-right'));
208 2
            $config->addComponent($pagination = new GridFieldPaginator(25));
209 2
            $config->addComponent(new GridFieldDetailForm());
210 2
            $config->addComponent(new GridFieldButtonRow('after'));
211 2
            $config->addComponent($export = new GridFieldExportButton('buttons-after-left'));
212 2
            $config->addComponent($print = new GridFieldPrintButton('buttons-after-left'));
213
214
            // show user form items in the summary tab
215
            $summaryarray = array(
216 2
                'ID' => 'ID',
217
                'Created' => 'Created',
218
                'LastEdited' => 'Last Edited'
219
            );
220 2
            foreach(EditableFormField::get()->filter(array("ParentID" => $parentID)) as $eff) {
221 2
                if($eff->ShowInSummary) {
222 1
                    $summaryarray[$eff->Name] = $eff->Title ?: $eff->Name;
223
                }
224
            }
225
226 2
            $config->getComponentByType('GridFieldDataColumns')->setDisplayFields($summaryarray);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface GridFieldComponent as the method setDisplayFields() does only exist in the following implementations of said interface: GridFieldDataColumns, GridFieldEditableColumns, GridFieldExternalLink.

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...
227
228
            /**
229
             * Support for {@link https://github.com/colymba/GridFieldBulkEditingTools}
230
             */
231 2
            if (class_exists('GridFieldBulkManager')) {
232
                $config->addComponent(new GridFieldBulkManager());
233
            }
234
235 2
            $sort->setThrowExceptionOnBadDataType(false);
236 2
            $filter->setThrowExceptionOnBadDataType(false);
237 2
            $pagination->setThrowExceptionOnBadDataType(false);
238
239
            // attach every column to the print view form
240 2
            $columns['Created'] = 'Created';
241 2
            $columns['SubmittedBy.Email'] = 'Submitter';
242 2
            $filter->setColumns($columns);
243
244
            // print configuration
245
246 2
            $print->setPrintHasHeader(true);
247 2
            $print->setPrintColumns($columns);
248
249
            // export configuration
250 2
            $export->setCsvHasHeader(true);
251 2
            $export->setExportColumns($columns);
252
253 2
            $submissions = GridField::create(
254 2
                'Submissions',
255 2
                _t('UserDefinedForm.SUBMISSIONS', 'Submissions'),
256 2
                $self->Submissions()->sort('Created', 'DESC'),
257 2
                $config
258
            );
259 2
            $fields->addFieldToTab('Root.Submissions', $submissions);
260 2
            $fields->addFieldToTab(
261 2
                'Root.FormOptions',
262 2
                CheckboxField::create(
263 2
                    'DisableSaveSubmissions',
264 2
                    _t('UserDefinedForm.SAVESUBMISSIONS', 'Disable Saving Submissions to Server')
265
                )
266
            );
267 2
        });
268
269 2
        $fields = parent::getCMSFields();
270
271 2
        if ($this->EmailRecipients()->Count() == 0 && static::config()->recipients_warning_enabled) {
272
            $fields->addFieldToTab("Root.Main", new LiteralField("EmailRecipientsWarning",
273
                "<p class=\"message warning\">" . _t("UserDefinedForm.NORECIPIENTS",
274
                "Warning: You have not configured any recipients. Form submissions may be missed.")
275
                . "</p>"), "Title");
276
        }
277
278 2
        return $fields;
279
    }
280
281
    /**
282
     * Allow overriding the EmailRecipients on a {@link DataExtension}
283
     * so you can customise who receives an email.
284
     * Converts the RelationList to an ArrayList so that manipulation
285
     * of the original source data isn't possible.
286
     *
287
     * @return ArrayList
288
     */
289 3
    public function FilteredEmailRecipients($data = null, $form = null)
290
    {
291 3
        $recipients = new ArrayList($this->EmailRecipients()->toArray());
292
293
        // Filter by rules
294
        $recipients = $recipients->filterByCallback(function ($recipient) use ($data, $form) {
295
            /** @var UserDefinedForm_EmailRecipient $recipient */
296 2
            return $recipient->canSend($data, $form);
297 3
        });
298
299 3
        $this->extend('updateFilteredEmailRecipients', $recipients, $data, $form);
300
301 3
        return $recipients;
302
    }
303
304
    /**
305
     * Custom options for the form. You can extend the built in options by
306
     * using {@link updateFormOptions()}
307
     *
308
     * @return FieldList
309
     */
310 3
    public function getFormOptions()
311
    {
312 3
        $submit = ($this->SubmitButtonText) ? $this->SubmitButtonText : _t('UserDefinedForm.SUBMITBUTTON', 'Submit');
313 3
        $clear = ($this->ClearButtonText) ? $this->ClearButtonText : _t('UserDefinedForm.CLEARBUTTON', 'Clear');
314
315 3
        $options = new FieldList(
316 3
            new TextField("SubmitButtonText", _t('UserDefinedForm.TEXTONSUBMIT', 'Text on submit button:'), $submit),
317 3
            new TextField("ClearButtonText", _t('UserDefinedForm.TEXTONCLEAR', 'Text on clear button:'), $clear),
318 3
            new CheckboxField("ShowClearButton", _t('UserDefinedForm.SHOWCLEARFORM', 'Show Clear Form Button'), $this->ShowClearButton),
319 3
            new CheckboxField("EnableLiveValidation", _t('UserDefinedForm.ENABLELIVEVALIDATION', 'Enable live validation')),
320 3
            new CheckboxField("DisplayErrorMessagesAtTop", _t('UserDefinedForm.DISPLAYERRORMESSAGESATTOP', 'Display error messages above the form?')),
321 3
            new CheckboxField('DisableCsrfSecurityToken', _t('UserDefinedForm.DISABLECSRFSECURITYTOKEN', 'Disable CSRF Token')),
322 3
            new CheckboxField('DisableAuthenicatedFinishAction', _t('UserDefinedForm.DISABLEAUTHENICATEDFINISHACTION', 'Disable Authentication on finish action'))
323
        );
324
325 3
        $this->extend('updateFormOptions', $options);
326
327 3
        return $options;
328
    }
329
330
    /**
331
     * Get the HTML id of the error container displayed above the form.
332
     *
333
     * @return string
334
     */
335
    public function getErrorContainerID()
336
    {
337
        return $this->config()->error_container_id;
338
    }
339
340
    public function requireDefaultRecords()
341
    {
342
        parent::requireDefaultRecords();
343
344
        if (!$this->config()->upgrade_on_build) {
345
            return;
346
        }
347
348
        // Perform migrations
349
        Injector::inst()
350
            ->create('UserFormsUpgradeService')
351
            ->setQuiet(true)
352
            ->run();
353
354
        DB::alteration_message('Migrated userforms', 'changed');
355
    }
356
357
358
    /**
359
     * Validate formfields
360
     */
361
    public function getCMSValidator()
362
    {
363
        return new UserFormValidator();
364
    }
365
}
366
367
/**
368
 * Controller for the {@link UserDefinedForm} page type.
369
 *
370
 * @package userforms
371
 */
372
373
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...
374
{
375
376
    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...
377
378
    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...
379
        'index',
380
        'ping',
381
        'Form',
382
        'finished'
383
    );
384
385 5
    public function init()
386
    {
387 5
        parent::init();
388
389 5
        $page = $this->data();
390
391
        // load the css
392 5
        if (!$page->config()->block_default_userforms_css) {
393 5
            Requirements::css(USERFORMS_DIR . '/css/UserForm.css');
394
        }
395
396
        // load the jquery
397 5
        if (!$page->config()->block_default_userforms_js) {
398 5
        $lang = i18n::get_lang_from_locale(i18n::get_locale());
399 5
        Requirements::javascript(FRAMEWORK_DIR .'/thirdparty/jquery/jquery.js');
400 5
        Requirements::javascript(USERFORMS_DIR . '/thirdparty/jquery-validate/jquery.validate.min.js');
401 5
        Requirements::add_i18n_javascript(USERFORMS_DIR . '/javascript/lang');
402 5
        Requirements::javascript(USERFORMS_DIR . '/javascript/UserForm.js');
403
404 5
        Requirements::javascript(
405 5
            USERFORMS_DIR . "/thirdparty/jquery-validate/localization/messages_{$lang}.min.js"
406
        );
407 5
        Requirements::javascript(
408 5
            USERFORMS_DIR . "/thirdparty/jquery-validate/localization/methods_{$lang}.min.js"
409
        );
410
411
        // Bind a confirmation message when navigating away from a partially completed form.
412 5
        if ($page::config()->enable_are_you_sure) {
413 5
            Requirements::javascript(USERFORMS_DIR . '/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js');
414
        }
415
    }
416 5
    }
417
418
    /**
419
     * Using $UserDefinedForm in the Content area of the page shows
420
     * where the form should be rendered into. If it does not exist
421
     * then default back to $Form.
422
     *
423
     * @return array
424
     */
425 6
    public function index()
426
    {
427 6
        if ($this->Content && $form = $this->Form()) {
428 6
            $hasLocation = stristr($this->Content, '$UserDefinedForm');
429 6
            if ($hasLocation) {
430 5
                $content = preg_replace('/(<p[^>]*>)?\\$UserDefinedForm(<\\/p>)?/i', $form->forTemplate(), $this->Content);
431
                return array(
432 5
                    'Content' => DBField::create_field('HTMLText', $content),
433 5
                    'Form' => ""
434
                );
435
            }
436
        }
437
438
        return array(
439 1
            'Content' => DBField::create_field('HTMLText', $this->Content),
440 1
            'Form' => $this->Form()
441
        );
442
    }
443
444
    /**
445
     * Keep the session alive for the user.
446
     *
447
     * @return int
448
     */
449
    public function ping()
450
    {
451
        return 1;
452
    }
453
454
    /**
455
     * Get the form for the page. Form can be modified by calling {@link updateForm()}
456
     * on a UserDefinedForm extension.
457
     *
458
     * @return Forms
459
     */
460 9
    public function Form()
461
    {
462 9
        $form = UserForm::create($this, 'Form_' . $this->ID);
463 9
        $form->setFormAction(Controller::join_links($this->Link(), 'Form'));
464 9
        $this->generateConditionalJavascript();
465 9
        return $form;
466
    }
467
468
    /**
469
     * Generate the javascript for the conditional field show / hiding logic.
470
     *
471
     * @return void
472
     */
473 9
    public function generateConditionalJavascript()
474
    {
475 9
        $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...
476 9
        $rules = "";
477
478 9
        $watch = array();
479
480 9
        if ($this->Fields()) {
481
            /** @var EditableFormField $field */
482 9
            foreach ($this->Fields() as $field) {
483 8
                if ($result = $field->formatDisplayRules()) {
484
                    $watch[] = $result;
485
                }
486
            }
487
        }
488 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...
489
            $rules .= $this->buildWatchJS($watch);
490
        }
491
492
        // Only add customScript if $default or $rules is defined
493 9
        if ($rules) {
494
            Requirements::customScript(<<<JS
495
                (function($) {
496
                    $(document).ready(function() {
497
                        {$rules}
498
                    });
499
                })(jQuery);
500
JS
501
, 'UserFormsConditional');
502
        }
503 9
    }
504
505
    /**
506
     * Process the form that is submitted through the site
507
     *
508
     * {@see UserForm::validate()} for validation step prior to processing
509
     *
510
     * @param array $data
511
     * @param Form $form
512
     *
513
     * @return Redirection
514
     */
515 2
    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...
516
    {
517 2
        $submittedForm = Object::create('SubmittedForm');
518 2
        $submittedForm->SubmittedByID = ($id = Member::currentUserID()) ? $id : 0;
519 2
        $submittedForm->ParentID = $this->ID;
520
521
        // if saving is not disabled save now to generate the ID
522 2
        if (!$this->DisableSaveSubmissions) {
523 2
            $submittedForm->write();
524
        }
525
526 2
        $attachments = array();
527 2
        $submittedFields = new ArrayList();
528
529 2
        foreach ($this->Fields() as $field) {
530 2
            if (!$field->showInReports()) {
531 2
                continue;
532
            }
533
534 2
            $submittedField = $field->getSubmittedFormField();
535 2
            $submittedField->ParentID = $submittedForm->ID;
536 2
            $submittedField->Name = $field->Name;
537 2
            $submittedField->Title = $field->getField('Title');
538
539
            // save the value from the data
540 2
            if ($field->hasMethod('getValueFromData')) {
541
                $submittedField->Value = $field->getValueFromData($data);
542
            } else {
543 2
                if (isset($data[$field->Name])) {
544 2
                    $submittedField->Value = $data[$field->Name];
545
                }
546
            }
547
548 2
            if (!empty($data[$field->Name])) {
549 2
                if (in_array("EditableFileField", $field->getClassAncestry())) {
550
                    if (!empty($_FILES[$field->Name]['name'])) {
551
                        $foldername = $field->getFormField()->getFolderName();
552
553
                        // create the file from post data
554
                        $upload = new Upload();
555
                        $file = new File();
556
                        $file->ShowInSearch = 0;
557
                        try {
558
                            $upload->loadIntoFile($_FILES[$field->Name], $file, $foldername);
559
                        } catch (ValidationException $e) {
560
                            $validationResult = $e->getResult();
561
                            $form->addErrorMessage($field->Name, $validationResult->message(), 'bad');
562
                            Controller::curr()->redirectBack();
563
                            return;
564
                        }
565
566
                        // write file to form field
567
                        $submittedField->UploadedFileID = $file->ID;
568
569
                        // attach a file only if lower than 1MB
570
                        if ($file->getAbsoluteSize() < 1024*1024*1) {
571
                            $attachments[] = $file;
572
                        }
573
                    }
574
                }
575
            }
576
577 2
            $submittedField->extend('onPopulationFromField', $field);
578
579 2
            if (!$this->DisableSaveSubmissions) {
580 2
                $submittedField->write();
581
            }
582
583 2
            $submittedFields->push($submittedField);
584
        }
585
586
        $emailData = array(
587 2
            "Sender" => Member::currentUser(),
588 2
            "Fields" => $submittedFields
589
        );
590
591 2
        $this->extend('updateEmailData', $emailData, $attachments);
592
593
        // email users on submit.
594 2
        if ($recipients = $this->FilteredEmailRecipients($data, $form)) {
595 2
            foreach ($recipients as $recipient) {
596 1
                $email = new UserFormRecipientEmail($submittedFields);
597 1
                $mergeFields = $this->getMergeFieldsMap($emailData['Fields']);
598
599 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...
600
                    foreach ($attachments as $file) {
601
                        if ($file->ID != 0) {
602
                            $email->attachFile(
603
                                $file->Filename,
604
                                $file->Filename,
605
                                HTTP::get_mime_type($file->Filename)
606
                            );
607
                        }
608
                    }
609
                }
610
611 1
                $parsedBody = SSViewer::execute_string($recipient->getEmailBodyContent(), $mergeFields);
612
613 1
                if (!$recipient->SendPlain && $recipient->emailTemplateExists()) {
614
                    $email->setTemplate($recipient->EmailTemplate);
615
                }
616
617 1
                $email->populateTemplate($recipient);
618 1
                $email->populateTemplate($emailData);
619 1
                $email->setFrom($recipient->EmailFrom);
620 1
                $email->setBody($parsedBody);
621 1
                $email->setTo($recipient->EmailAddress);
622 1
                $email->setSubject($recipient->EmailSubject);
623
624 1
                if ($recipient->EmailReplyTo) {
625
                    $email->setReplyTo($recipient->EmailReplyTo);
626
                }
627
628
                // check to see if they are a dynamic reply to. eg based on a email field a user selected
629 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...
630 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailFromField()->Name);
631
632 1
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
633
                        $email->setReplyTo($submittedFormField->Value);
634
                    }
635
                }
636
                // check to see if they are a dynamic reciever eg based on a dropdown field a user selected
637 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...
638 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailToField()->Name);
639
640 1
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
641
                        $email->setTo($submittedFormField->Value);
642
                    }
643
                }
644
645
                // check to see if there is a dynamic subject
646 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...
647 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailSubjectField()->Name);
648
649 1
                    if ($submittedFormField && trim($submittedFormField->Value)) {
650
                        $email->setSubject($submittedFormField->Value);
651
                    }
652
                }
653
654 1
                $this->extend('updateEmail', $email, $recipient, $emailData);
655
656 1
                if ($recipient->SendPlain) {
657 1
                    $body = strip_tags($recipient->getEmailBodyContent()) . "\n";
658 1
                    if (isset($emailData['Fields']) && !$recipient->HideFormData) {
659 1
                        foreach ($emailData['Fields'] as $Field) {
660 1
                            $body .= $Field->Title .': '. $Field->Value ." \n";
661
                        }
662
                    }
663
664 1
                    $email->setBody($body);
665 1
                    $email->sendPlain();
666
                } else {
667 1
                    $email->send();
668
                }
669
            }
670
        }
671
672 2
        $submittedForm->extend('updateAfterProcess');
673
674 2
        Session::clear("FormInfo.{$form->FormName()}.errors");
675 2
        Session::clear("FormInfo.{$form->FormName()}.data");
676
677 2
        $referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
678
679
        // set a session variable from the security ID to stop people accessing
680
        // the finished method directly.
681 2
        if (!$this->DisableAuthenicatedFinishAction) {
682 2
            if (isset($data['SecurityID'])) {
683
                Session::set('FormProcessed', $data['SecurityID']);
684
            } else {
685
                // if the form has had tokens disabled we still need to set FormProcessed
686
                // to allow us to get through the finshed method
687 2
                if (!$this->Form()->getSecurityToken()->isEnabled()) {
688 2
                    $randNum = rand(1, 1000);
689 2
                    $randHash = md5($randNum);
690 2
                    Session::set('FormProcessed', $randHash);
691 2
                    Session::set('FormProcessedNum', $randNum);
692
                }
693
            }
694
        }
695
696 2
        if (!$this->DisableSaveSubmissions) {
697 2
            Session::set('userformssubmission'. $this->ID, $submittedForm->ID);
698
        }
699
700 2
        return $this->redirect($this->Link('finished') . $referrer . $this->config()->finished_anchor);
701
    }
702
703
    /**
704
     * Allows the use of field values in email body.
705
     *
706
     * @param ArrayList fields
707
     * @return ArrayData
708
     */
709 1
    private function getMergeFieldsMap($fields = array())
710
    {
711 1
        $data = new ArrayData(array());
712
713 1
        foreach ($fields as $field) {
714 1
            $data->setField($field->Name, DBField::create_field('Text', $field->Value));
715
        }
716
717 1
        return $data;
718
    }
719
720
    /**
721
     * This action handles rendering the "finished" message, which is
722
     * customizable by editing the ReceivedFormSubmission template.
723
     *
724
     * @return ViewableData
725
     */
726 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...
727
    {
728 3
        $submission = Session::get('userformssubmission'. $this->ID);
729
730 3
        if ($submission) {
731 1
            $submission = SubmittedForm::get()->byId($submission);
732
        }
733
734 3
        $referrer = isset($_GET['referrer']) ? urldecode($_GET['referrer']) : null;
735
736 3
        if (!$this->DisableAuthenicatedFinishAction) {
737 3
            $formProcessed = Session::get('FormProcessed');
738
739 3
            if (!isset($formProcessed)) {
740 1
                return $this->redirect($this->Link() . $referrer);
741
            } else {
742 2
                $securityID = Session::get('SecurityID');
743
                // make sure the session matches the SecurityID and is not left over from another form
744 2
                if ($formProcessed != $securityID) {
745
                    // they may have disabled tokens on the form
746 1
                    $securityID = md5(Session::get('FormProcessedNum'));
747 1
                    if ($formProcessed != $securityID) {
748
                        return $this->redirect($this->Link() . $referrer);
749
                    }
750
                }
751
            }
752
753 2
            Session::clear('FormProcessed');
754
        }
755
756
        $data = array(
757 2
                'Submission' => $submission,
758 2
                'Link' => $referrer
759
        );
760
761 2
        $this->extend('updateReceivedFormSubmissionData', $data);
762
763 2
        return $this->customise(array(
764 2
            'Content' => $this->customise($data)->renderWith('ReceivedFormSubmission'),
765 2
            'Form' => '',
766
        ));
767
    }
768
769
    /**
770
     * Outputs the required JS from the $watch input
771
     *
772
     * @param array $watch
773
     *
774
     * @return string
775
     */
776
    protected function buildWatchJS($watch)
777
    {
778
        $result = '';
779
        foreach ($watch as $key => $rule) {
780
            $events = implode(' ', $rule['events']);
781
            $selectors = implode(', ', $rule['selectors']);
782
            $conjunction = $rule['conjunction'];
783
            $operations = implode(" {$conjunction} ", $rule['operations']);
784
            $target = $rule['targetFieldID'];
785
786
            $result .= <<<EOS
787
\n
788
    $('.userform').on('{$events}',
789
    "{$selectors}",
790
    function (){
791
        if ({$operations}) {
792
            $('{$target}').{$rule['view']};
793
        } else {
794
            $('{$target}').{$rule['opposite']};
795
        }
796
    });
797
    $("{$target}").find('.hide').removeClass('hide');
798
EOS;
799
        }
800
801
        return $result;
802
    }
803
}
804