Completed
Pull Request — master (#562)
by
unknown
17:46
created

generateConditionalJavascript()   D

Complexity

Conditions 35
Paths 16

Size

Total Lines 185
Code Lines 106

Duplication

Lines 22
Ratio 11.89 %

Code Coverage

Tests 25
CRAP Score 646.8045

Importance

Changes 0
Metric Value
dl 22
loc 185
ccs 25
cts 121
cp 0.2066
rs 4.297
c 0
b 0
f 0
cc 35
eloc 106
nc 16
nop 0
crap 646.8045

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 2
     public function getCMSFields()
122 2
     {
123 2
         Requirements::css(USERFORMS_DIR . '/css/UserForm_cms.css');
124
125 2
         $self = $this;
126
127
         $this->beforeUpdateCMSFields(function ($fields) use ($self) {
128
129
            // define tabs
130 2
            $fields->findOrMakeTab('Root.FormOptions', _t('UserDefinedForm.CONFIGURATION', 'Configuration'));
131 2
            $fields->findOrMakeTab('Root.Recipients', _t('UserDefinedForm.RECIPIENTS', 'Recipients'));
132 2
            $fields->findOrMakeTab('Root.Submissions', _t('UserDefinedForm.SUBMISSIONS', 'Submissions'));
133
134
            // text to show on complete
135 2
            $onCompleteFieldSet = new CompositeField(
136 2
                $label = new LabelField('OnCompleteMessageLabel', _t('UserDefinedForm.ONCOMPLETELABEL', 'Show on completion')),
137 2
                $editor = new HtmlEditorField('OnCompleteMessage', '', _t('UserDefinedForm.ONCOMPLETEMESSAGE', $self->OnCompleteMessage))
138 2
            );
139
140 2
            $onCompleteFieldSet->addExtraClass('field');
141
142 2
            $editor->setRows(3);
143 2
            $label->addExtraClass('left');
144
145
            // Define config for email recipients
146 2
            $emailRecipientsConfig = GridFieldConfig_RecordEditor::create(10);
147 2
            $emailRecipientsConfig->getComponentByType('GridFieldAddNewButton')
148 2
                ->setButtonName(
149 2
                    _t('UserDefinedForm.ADDEMAILRECIPIENT', 'Add Email Recipient')
150 2
                );
151
152
            // who do we email on submission
153 2
            $emailRecipients = new GridField(
154 2
                'EmailRecipients',
155 2
                _t('UserDefinedForm.EMAILRECIPIENTS', 'Email Recipients'),
156 2
                $self->EmailRecipients(),
157
                $emailRecipientsConfig
158 2
            );
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 2
                ->getConfig()
161 2
                ->getComponentByType('GridFieldDetailForm')
162 2
                ->setItemRequestClass('UserFormRecipientItemRequest');
163
164 2
            $fields->addFieldsToTab('Root.FormOptions', $onCompleteFieldSet);
165 2
            $fields->addFieldToTab('Root.Recipients', $emailRecipients);
166 2
            $fields->addFieldsToTab('Root.FormOptions', $self->getFormOptions());
167
168
169
            // view the submissions
170 2
            $submissions = new GridField(
171 2
                'Submissions',
172 2
                _t('UserDefinedForm.SUBMISSIONS', 'Submissions'),
173 2
                 $self->Submissions()->sort('Created', 'DESC')
174 2
            );
175
176
            // make sure a numeric not a empty string is checked against this int column for SQL server
177 2
            $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 2
WHERE "SubmittedForm"."ParentID" = '$parentID'
186 2
ORDER BY "Sort", "Title"
187 2
SQL;
188
            // Sanitise periods in title
189 2
            $columns = array();
190 2
            foreach (DB::query($columnSQL)->map() as $name => $title) {
191
                $columns[$name] = trim(strtr($title, '.', ' '));
192 2
            }
193
194 2
            $config = new GridFieldConfig();
195 2
            $config->addComponent(new GridFieldToolbarHeader());
196 2
            $config->addComponent($sort = new GridFieldSortableHeader());
197 2
            $config->addComponent($filter = new UserFormsGridFieldFilterHeader());
198 2
            $config->addComponent(new GridFieldDataColumns());
199 2
            $config->addComponent(new GridFieldEditButton());
200 2
            $config->addComponent(new GridFieldDeleteAction());
201 2
            $config->addComponent(new GridFieldPageCount('toolbar-header-right'));
202 2
            $config->addComponent($pagination = new GridFieldPaginator(25));
203 2
            $config->addComponent(new GridFieldDetailForm());
204 2
            $config->addComponent(new GridFieldButtonRow('after'));
205 2
            $config->addComponent($export = new GridFieldExportButton('buttons-after-left'));
206 2
            $config->addComponent($print = new GridFieldPrintButton('buttons-after-left'));
207
208
            // show user form items in the summary tab
209
            $summaryarray = array(
210 2
                'ID' => 'ID',
211 2
                'Created' => 'Created',
212
                'LastEdited' => 'Last Edited'
213 2
            );
214 2
            foreach(EditableFormField::get()->filter(array("ParentID" => $parentID)) as $eff) {
215 2
                if($eff->ShowInSummary) {
216 1
                    $summaryarray[$eff->Name] = $eff->Title ?: $eff->Name;
217 1
                }
218 2
            }
219
220 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...
221
222
            /**
223
             * Support for {@link https://github.com/colymba/GridFieldBulkEditingTools}
224
             */
225 2
            if (class_exists('GridFieldBulkManager')) {
226
                $config->addComponent(new GridFieldBulkManager());
227
            }
228
229 2
            $sort->setThrowExceptionOnBadDataType(false);
230 2
            $filter->setThrowExceptionOnBadDataType(false);
231 2
            $pagination->setThrowExceptionOnBadDataType(false);
232
233
            // attach every column to the print view form
234 2
            $columns['Created'] = 'Created';
235 2
            $filter->setColumns($columns);
236
237
            // print configuration
238
239 2
            $print->setPrintHasHeader(true);
240 2
            $print->setPrintColumns($columns);
241
242
            // export configuration
243 2
            $export->setCsvHasHeader(true);
244 2
            $export->setExportColumns($columns);
245
246 2
            $submissions->setConfig($config);
247 2
            $fields->addFieldToTab('Root.Submissions', $submissions);
248 2
            $fields->addFieldToTab('Root.FormOptions', new CheckboxField('DisableSaveSubmissions', _t('UserDefinedForm.SAVESUBMISSIONS', 'Disable Saving Submissions to Server')));
249
250 2
        });
251
252 2
         $fields = parent::getCMSFields();
253
254 2
         if ($this->EmailRecipients()->Count() == 0 && static::config()->recipients_warning_enabled) {
255
             $fields->addFieldToTab("Root.Main", new LiteralField("EmailRecipientsWarning",
256
                "<p class=\"message warning\">" . _t("UserDefinedForm.NORECIPIENTS",
257
                "Warning: You have not configured any recipients. Form submissions may be missed.")
258
                . "</p>"), "Title");
259
         }
260
261 2
         return $fields;
262
     }
263
264
    /**
265
     * Allow overriding the EmailRecipients on a {@link DataExtension}
266
     * so you can customise who receives an email.
267
     * Converts the RelationList to an ArrayList so that manipulation
268
     * of the original source data isn't possible.
269
     *
270
     * @return ArrayList
271
     */
272 3
    public function FilteredEmailRecipients($data = null, $form = null)
273
    {
274 3
        $recipients = new ArrayList($this->EmailRecipients()->toArray());
275
276
        // Filter by rules
277
        $recipients = $recipients->filterByCallback(function ($recipient) use ($data, $form) {
278 2
            return $recipient->canSend($data, $form);
279 3
        });
280
281 3
        $this->extend('updateFilteredEmailRecipients', $recipients, $data, $form);
282
283 3
        return $recipients;
284
    }
285
286
    /**
287
     * Custom options for the form. You can extend the built in options by
288
     * using {@link updateFormOptions()}
289
     *
290
     * @return FieldList
291
     */
292 3
    public function getFormOptions()
293
    {
294 3
        $submit = ($this->SubmitButtonText) ? $this->SubmitButtonText : _t('UserDefinedForm.SUBMITBUTTON', 'Submit');
295 3
        $clear = ($this->ClearButtonText) ? $this->ClearButtonText : _t('UserDefinedForm.CLEARBUTTON', 'Clear');
296
297 3
        $options = new FieldList(
298 3
            new TextField("SubmitButtonText", _t('UserDefinedForm.TEXTONSUBMIT', 'Text on submit button:'), $submit),
299 3
            new TextField("ClearButtonText", _t('UserDefinedForm.TEXTONCLEAR', 'Text on clear button:'), $clear),
300 3
            new CheckboxField("ShowClearButton", _t('UserDefinedForm.SHOWCLEARFORM', 'Show Clear Form Button'), $this->ShowClearButton),
301 3
            new CheckboxField("EnableLiveValidation", _t('UserDefinedForm.ENABLELIVEVALIDATION', 'Enable live validation')),
302 3
            new CheckboxField("HideFieldLabels", _t('UserDefinedForm.HIDEFIELDLABELS', 'Hide field labels')),
303 3
            new CheckboxField("DisplayErrorMessagesAtTop", _t('UserDefinedForm.DISPLAYERRORMESSAGESATTOP', 'Display error messages above the form?')),
304 3
            new CheckboxField('DisableCsrfSecurityToken', _t('UserDefinedForm.DISABLECSRFSECURITYTOKEN', 'Disable CSRF Token')),
305 3
            new CheckboxField('DisableAuthenicatedFinishAction', _t('UserDefinedForm.DISABLEAUTHENICATEDFINISHACTION', 'Disable Authentication on finish action'))
306 3
        );
307
308 3
        $this->extend('updateFormOptions', $options);
309
310 3
        return $options;
311
    }
312
313
    /**
314
     * Get the HTML id of the error container displayed above the form.
315
     *
316
     * @return string
317
     */
318
    public function getErrorContainerID()
319
    {
320
        return $this->config()->error_container_id;
321
    }
322
323
    public function requireDefaultRecords()
324
    {
325
        parent::requireDefaultRecords();
326
327
        if (!$this->config()->upgrade_on_build) {
328
            return;
329
        }
330
331
        // Perform migrations
332
        Injector::inst()
333
            ->create('UserFormsUpgradeService')
334
            ->setQuiet(true)
335
            ->run();
336
337
        DB::alteration_message('Migrated userforms', 'changed');
338
    }
339
340
341
    /**
342
     * Validate formfields
343
     */
344
    public function getCMSValidator()
345
    {
346
        return new UserFormValidator();
347
    }
348
}
349
350
/**
351
 * Controller for the {@link UserDefinedForm} page type.
352
 *
353
 * @package userforms
354
 */
355
356
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...
357
{
358
359
    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...
360
361
    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...
362
        'index',
363
        'ping',
364
        'Form',
365
        'finished'
366
    );
367
368 6
    public function init()
369
    {
370 5
        parent::init();
371
372 5
        $page = $this->data();
373
374
        // load the css
375 5
        if (!$page->config()->block_default_userforms_css) {
376 5
            Requirements::css(USERFORMS_DIR . '/css/UserForm.css');
377 5
        }
378
379
        // load the jquery
380 5
        if (!$page->config()->block_default_userforms_js) {
381 5
            $lang = i18n::get_lang_from_locale(i18n::get_locale());
382 5
            Requirements::javascript(FRAMEWORK_DIR .'/thirdparty/jquery/jquery.js');
383 5
            Requirements::javascript(USERFORMS_DIR . '/thirdparty/jquery-validate/jquery.validate.min.js');
384 6
            Requirements::add_i18n_javascript(USERFORMS_DIR . '/javascript/lang');
385 6
            Requirements::javascript(USERFORMS_DIR . '/javascript/UserForm.js');
386
387 5
            Requirements::javascript(
388 5
                USERFORMS_DIR . "/thirdparty/jquery-validate/localization/messages_{$lang}.min.js"
389 5
            );
390 5
            Requirements::javascript(
391 5
                USERFORMS_DIR . "/thirdparty/jquery-validate/localization/methods_{$lang}.min.js"
392 5
            );
393 5
            if ($this->HideFieldLabels) {
394
                Requirements::javascript(USERFORMS_DIR . '/thirdparty/Placeholders.js/Placeholders.min.js');
395
            }
396
397
            // Bind a confirmation message when navigating away from a partially completed form.
398 5
            if ($page::config()->enable_are_you_sure) {
399 5
                Requirements::javascript(USERFORMS_DIR . '/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js');
400 5
            }
401 5
        }
402 5
    }
403
404
    /**
405
     * Using $UserDefinedForm in the Content area of the page shows
406
     * where the form should be rendered into. If it does not exist
407
     * then default back to $Form.
408
     *
409
     * @return array
410
     */
411 6
    public function index()
412
    {
413 6
        if ($this->Content && $form = $this->Form()) {
414 6
            $hasLocation = stristr($this->Content, '$UserDefinedForm');
415 6
            if ($hasLocation) {
416 5
                $content = preg_replace('/(<p[^>]*>)?\\$UserDefinedForm(<\\/p>)?/i', $form->forTemplate(), $this->Content);
417
                return array(
418 5
                    'Content' => DBField::create_field('HTMLText', $content),
419
                    'Form' => ""
420 5
                );
421
            }
422 1
        }
423
424
        return array(
425 1
            'Content' => DBField::create_field('HTMLText', $this->Content),
426 1
            'Form' => $this->Form()
427 1
        );
428
    }
429
430
    /**
431
     * Keep the session alive for the user.
432
     *
433
     * @return int
434
     */
435
    public function ping()
436
    {
437
        return 1;
438
    }
439
440
    /**
441
     * Get the form for the page. Form can be modified by calling {@link updateForm()}
442
     * on a UserDefinedForm extension.
443
     *
444
     * @return Forms
445
     */
446 9
    public function Form()
447
    {
448 9
        $form = UserForm::create($this);
449 9
        $this->generateConditionalJavascript();
450 9
        return $form;
451
    }
452
453
    /**
454
     * Generate the javascript for the conditional field show / hiding logic.
455
     *
456
     * @return void
457
     */
458 9
    public function generateConditionalJavascript()
459
    {
460 9
        $default = "";
461 9
        $rules = "";
462
463 9
        $watch = array();
464 9
        $watchLoad = array();
465
466 9
        if ($this->Fields()) {
467 9
            foreach ($this->Fields() as $field) {
468 8
                $holderSelector = $field->getSelectorHolder();
469
470
                // Is this Field Show by Default
471 8
                if (!$field->ShowOnLoad) {
472
                    $default .= "{$holderSelector}.hide().trigger('userform.field.hide');\n";
473
                }
474
475
                // Check for field dependencies / default
476 8
                foreach ($field->EffectiveDisplayRules() as $rule) {
477
478
                    // Get the field which is effected
479
                    $formFieldWatch = EditableFormField::get()->byId($rule->ConditionFieldID);
480
481
                    // Skip deleted fields
482
                    if (!$formFieldWatch) {
483
                        continue;
484
                    }
485
486
                    $fieldToWatch = $formFieldWatch->getSelectorField($rule);
487
                    $fieldToWatchOnLoad = $formFieldWatch->getSelectorField($rule, true);
488
489
                    // show or hide?
490
                    $view = ($rule->Display == 'Hide') ? 'hide' : 'show';
491
                    $opposite = ($view == "show") ? "hide" : "show";
492
493
                    // what action do we need to keep track of. Something nicer here maybe?
494
                    // @todo encapulsation
495
                    $action = "change";
496
497
                    if ($formFieldWatch instanceof EditableTextField) {
498
                        $action = "keyup";
499
                    }
500
501
                    // is this field a special option field
502
                    $checkboxField = false;
503
                    $radioField = false;
504
505
                    if (in_array($formFieldWatch->ClassName, array('EditableCheckboxGroupField', 'EditableCheckbox'))) {
506
                        $action = "click";
507
                        $checkboxField = true;
508
                    } elseif ($formFieldWatch->ClassName == "EditableRadioField") {
509
                        $radioField = true;
510
                    }
511
512
                    // and what should we evaluate
513
                    switch ($rule->ConditionOption) {
514
                        case 'IsNotBlank':
515
                            $expression = ($checkboxField || $radioField) ? '$(this).is(":checked")' :'$(this).val() != ""';
516
517
                            break;
518
                        case 'IsBlank':
519
                            $expression = ($checkboxField || $radioField) ? '!($(this).is(":checked"))' : '$(this).val() == ""';
520
521
                            break;
522 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...
523
                            if ($checkboxField) {
524
                                $expression = '$(this).prop("checked")';
525
                            } elseif ($radioField) {
526
                                // We cannot simply get the value of the radio group, we need to find the checked option first.
527
                                $expression = '$(this).closest(".field, .control-group").find("input:checked").val()=="'. $rule->FieldValue .'"';
528
                            } else {
529
                                $expression = '$(this).val() == "'. $rule->FieldValue .'"';
530
                            }
531
532
                            break;
533
                        case 'ValueLessThan':
534
                            $expression = '$(this).val() < parseFloat("'. $rule->FieldValue .'")';
535
536
                            break;
537
                        case 'ValueLessThanEqual':
538
                            $expression = '$(this).val() <= parseFloat("'. $rule->FieldValue .'")';
539
540
                            break;
541
                        case 'ValueGreaterThan':
542
                            $expression = '$(this).val() > parseFloat("'. $rule->FieldValue .'")';
543
544
                            break;
545
                        case 'ValueGreaterThanEqual':
546
                            $expression = '$(this).val() >= parseFloat("'. $rule->FieldValue .'")';
547
548 1
                            break;
549 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...
550
                            if ($checkboxField) {
551
                                $expression = '!$(this).prop("checked")';
552
                            } elseif ($radioField) {
553
                                // We cannot simply get the value of the radio group, we need to find the checked option first.
554
                                $expression = '$(this).parents(".field, .control-group").find("input:checked").val()!="'. $rule->FieldValue .'"';
555
                            } else {
556 1
                                $expression = '$(this).val() != "'. $rule->FieldValue .'"';
557
                            }
558
559
                            break;
560
                    }
561
562
                    if (!isset($watch[$fieldToWatch])) {
563
                        $watch[$fieldToWatch] = array();
564 1
                    }
565
566
                    $watch[$fieldToWatch][] = array(
567
                        'expression' => $expression,
568
                        'holder_selector' => $holderSelector,
569
                        'view' => $view,
570
                        'opposite' => $opposite,
571
                        'action' => $action
572
                    );
573
574
                    $watchLoad[$fieldToWatchOnLoad] = true;
575 8
                }
576 9
            }
577 9
        }
578
579 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...
580
            foreach ($watch as $key => $values) {
581
                $logic = array();
582
                $actions = array();
583
584
                foreach ($values as $rule) {
585
                    // Assign action
586
                    $actions[$rule['action']] = $rule['action'];
587
588
                    // Assign behaviour
589
                    $expression = $rule['expression'];
590
                    $holder = $rule['holder_selector'];
591
                    $view = $rule['view']; // hide or show
592
                    $opposite = $rule['opposite'];
593
                    // Generated javascript for triggering visibility
594
                    $logic[] = <<<"EOS"
595
if({$expression}) {
596
	{$holder}
597
		.{$view}()
598
		.trigger('userform.field.{$view}');
599
} else {
600
	{$holder}
601 1
		.{$opposite}()
602
		.trigger('userform.field.{$opposite}');
603
}
604
EOS;
605
                }
606
607
                $logic = implode("\n", $logic);
608
                $rules .= $key.".each(function() {\n
609
	$(this).data('userformConditions', function() {\n
610
		$logic\n
611
	}); \n
612
});\n";
613
                foreach ($actions as $action) {
614
                    $rules .= $key.".$action(function() {
615
	$(this).data('userformConditions').call(this);\n
616 1
});\n";
617
                }
618
            }
619
        }
620
621 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...
622
            foreach ($watchLoad as $key => $value) {
623
                $rules .= $key.".each(function() {
624 1
	$(this).data('userformConditions').call(this);\n
625
});\n";
626 3
            }
627
        }
628
629
        // Only add customScript if $default or $rules is defined
630 9
        if ($default  || $rules) {
631
            Requirements::customScript(<<<JS
632 1
				(function($) {
633
					$(document).ready(function() {
634
						$default
635
636
						$rules
637
					})
638
				})(jQuery);
639
JS
640
, 'UserFormsConditional');
641
        }
642 9
    }
643
644
    /**
645
     * Process the form that is submitted through the site
646
     *
647
     * {@see UserForm::validate()} for validation step prior to processing
648
     *
649
     * @param array $data
650
     * @param Form $form
651
     *
652
     * @return Redirection
653
     */
654 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...
655
    {
656 2
        $submittedForm = Object::create('SubmittedForm');
657 2
        $submittedForm->SubmittedByID = ($id = Member::currentUserID()) ? $id : 0;
658 2
        $submittedForm->ParentID = $this->ID;
659
660
        // if saving is not disabled save now to generate the ID
661 2
        if (!$this->DisableSaveSubmissions) {
662 2
            $submittedForm->write();
663 2
        }
664
665 2
        $attachments = array();
666 2
        $submittedFields = new ArrayList();
667
668 2
        foreach ($this->Fields() as $field) {
669 3
            if (!$field->showInReports()) {
670 2
                continue;
671
            }
672
673 2
            $submittedField = $field->getSubmittedFormField();
674 2
            $submittedField->ParentID = $submittedForm->ID;
675 2
            $submittedField->Name = $field->Name;
676 2
            $submittedField->Title = $field->getField('Title');
677
678
            // save the value from the data
679 2
            if ($field->hasMethod('getValueFromData')) {
680
                $submittedField->Value = $field->getValueFromData($data);
681
            } else {
682 2
                if (isset($data[$field->Name])) {
683 2
                    $submittedField->Value = $data[$field->Name];
684 2
                }
685
            }
686
687 2
            if (!empty($data[$field->Name])) {
688 2
                if (in_array("EditableFileField", $field->getClassAncestry())) {
689
                    if (!empty($_FILES[$field->Name]['name'])) {
690
                        $foldername = $field->getFormField()->getFolderName();
691
692
                        // create the file from post data
693
                        $upload = new Upload();
694
                        $file = new File();
695
                        $file->ShowInSearch = 0;
696
                        try {
697
                            $upload->loadIntoFile($_FILES[$field->Name], $file, $foldername);
698
                        } catch (ValidationException $e) {
699
                            $validationResult = $e->getResult();
700
                            $form->addErrorMessage($field->Name, $validationResult->message(), 'bad');
701
                            Controller::curr()->redirectBack();
702
                            return;
703
                        }
704
705
                        // write file to form field
706
                        $submittedField->UploadedFileID = $file->ID;
707
708
                        // attach a file only if lower than 1MB
709
                        if ($file->getAbsoluteSize() < 1024*1024*1) {
710
                            $attachments[] = $file;
711
                        }
712
                    }
713
                }
714 2
            }
715
716 2
            $submittedField->extend('onPopulationFromField', $field);
717
718 2
            if (!$this->DisableSaveSubmissions) {
719 2
                $submittedField->write();
720 2
            }
721
722 2
            $submittedFields->push($submittedField);
723 2
        }
724
725
        $emailData = array(
726 2
            "Sender" => Member::currentUser(),
727
            "Fields" => $submittedFields
728 2
        );
729
730 2
        $this->extend('updateEmailData', $emailData, $attachments);
731
732
        // email users on submit.
733 2
        if ($recipients = $this->FilteredEmailRecipients($data, $form)) {
734 2
            foreach ($recipients as $recipient) {
735 1
                $email = new UserFormRecipientEmail($submittedFields);
736 1
                $mergeFields = $this->getMergeFieldsMap($emailData['Fields']);
737
738 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...
739
                    foreach ($attachments as $file) {
740
                        if ($file->ID != 0) {
741
                            $email->attachFile(
742
                                $file->Filename,
743
                                $file->Filename,
744
                                HTTP::get_mime_type($file->Filename)
745
                            );
746
                        }
747
                    }
748
                }
749
750 1
                $parsedBody = SSViewer::execute_string($recipient->getEmailBodyContent(), $mergeFields);
751
752 1
                if (!$recipient->SendPlain && $recipient->emailTemplateExists()) {
753
                    $email->setTemplate($recipient->EmailTemplate);
754
                }
755
756 1
                $email->populateTemplate($recipient);
757 1
                $email->populateTemplate($emailData);
758 1
                $email->setFrom($recipient->EmailFrom);
759 1
                $email->setBody($parsedBody);
760 1
                $email->setTo($recipient->EmailAddress);
761 1
                $email->setSubject($recipient->EmailSubject);
762
763 1
                if ($recipient->EmailReplyTo) {
764
                    $email->setReplyTo($recipient->EmailReplyTo);
765
                }
766
767
                // check to see if they are a dynamic reply to. eg based on a email field a user selected
768 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...
769 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailFromField()->Name);
770
771 1
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
772
                        $email->setReplyTo($submittedFormField->Value);
773
                    }
774 1
                }
775
                // check to see if they are a dynamic reciever eg based on a dropdown field a user selected
776 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...
777 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailToField()->Name);
778
779 1
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
780
                        $email->setTo($submittedFormField->Value);
781
                    }
782 1
                }
783
784
                // check to see if there is a dynamic subject
785 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...
786 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailSubjectField()->Name);
787
788 1
                    if ($submittedFormField && trim($submittedFormField->Value)) {
789
                        $email->setSubject($submittedFormField->Value);
790
                    }
791 1
                }
792
793 1
                $this->extend('updateEmail', $email, $recipient, $emailData);
794
795 1
                if ($recipient->SendPlain) {
796 1
                    $body = strip_tags($recipient->getEmailBodyContent()) . "\n";
797 1
                    if (isset($emailData['Fields']) && !$recipient->HideFormData) {
798 1
                        foreach ($emailData['Fields'] as $Field) {
799 1
                            $body .= $Field->Title .': '. $Field->Value ." \n";
800 1
                        }
801 1
                    }
802
803 1
                    $email->setBody($body);
804 1
                    $email->sendPlain();
805 1
                } else {
806 1
                    $email->send();
807
                }
808 2
            }
809 2
        }
810
811 2
        $submittedForm->extend('updateAfterProcess');
812
813 2
        Session::clear("FormInfo.{$form->FormName()}.errors");
814 2
        Session::clear("FormInfo.{$form->FormName()}.data");
815
816 2
        $referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
817
818
        // set a session variable from the security ID to stop people accessing
819
        // the finished method directly.
820 2
        if (!$this->DisableAuthenicatedFinishAction) {
821 2
            if (isset($data['SecurityID'])) {
822
                Session::set('FormProcessed', $data['SecurityID']);
823
            } else {
824
                // if the form has had tokens disabled we still need to set FormProcessed
825
                // to allow us to get through the finshed method
826 2
                if (!$this->Form()->getSecurityToken()->isEnabled()) {
827 2
                    $randNum = rand(1, 1000);
828 2
                    $randHash = md5($randNum);
829 2
                    Session::set('FormProcessed', $randHash);
830 2
                    Session::set('FormProcessedNum', $randNum);
831 2
                }
832
            }
833 2
        }
834
835 2
        if (!$this->DisableSaveSubmissions) {
836 2
            Session::set('userformssubmission'. $this->ID, $submittedForm->ID);
837 2
        }
838
839 2
        return $this->redirect($this->Link('finished') . $referrer . $this->config()->finished_anchor);
840
    }
841
842
    /**
843
     * Allows the use of field values in email body.
844
     *
845
     * @param ArrayList fields
846
     * @return ArrayData
847
     */
848 1
    private function getMergeFieldsMap($fields = array())
849
    {
850 1
        $data = new ArrayData(array());
851
852 1
        foreach ($fields as $field) {
853 1
            $data->setField($field->Name, DBField::create_field('Text', $field->Value));
854 1
        }
855
856 1
        return $data;
857
    }
858
859
    /**
860
     * This action handles rendering the "finished" message, which is
861
     * customizable by editing the ReceivedFormSubmission template.
862
     *
863
     * @return ViewableData
864
     */
865 8
    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...
866
    {
867 3
        $submission = Session::get('userformssubmission'. $this->ID);
868
869 3
        if ($submission) {
870 1
            $submission = SubmittedForm::get()->byId($submission);
871 1
        }
872
873 3
        $referrer = isset($_GET['referrer']) ? urldecode($_GET['referrer']) : null;
874
875 3
        if (!$this->DisableAuthenicatedFinishAction) {
876 3
            $formProcessed = Session::get('FormProcessed');
877
878 3
            if (!isset($formProcessed)) {
879 1
                return $this->redirect($this->Link() . $referrer);
880
            } else {
881 2
                $securityID = Session::get('SecurityID');
882
                // make sure the session matches the SecurityID and is not left over from another form
883 2
                if ($formProcessed != $securityID) {
884
                    // they may have disabled tokens on the form
885 1
                    $securityID = md5(Session::get('FormProcessedNum'));
886 1
                    if ($formProcessed != $securityID) {
887
                        return $this->redirect($this->Link() . $referrer);
888
                    }
889 1
                }
890
            }
891
892 2
            Session::clear('FormProcessed');
893 2
        }
894
895 2
        return $this->customise(array(
896 2
            'Content' => $this->customise(array(
897 2
                'Submission' => $submission,
898
                'Link' => $referrer
899 2
            ))->renderWith('ReceivedFormSubmission'),
900 2
            'Form' => '',
901 2
        ));
902 8
    }
903
}
904