Completed
Pull Request — master (#539)
by
unknown
35:41
created

UserDefinedForm::getCMSFields()   D

Complexity

Conditions 9
Paths 2

Size

Total Lines 142
Code Lines 84

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 74
CRAP Score 9.0753

Importance

Changes 0
Metric Value
dl 0
loc 142
ccs 74
cts 82
cp 0.9024
rs 4.8196
c 0
b 0
f 0
cc 9
eloc 84
nc 2
nop 0
crap 9.0753

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @package userforms
5
 */
6
7
class UserDefinedForm extends Page
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

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

namespace YourVendor;

class YourClass { }

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

Loading history...
8
{
9
    
10
    /**
11
     * @var string
12
     */
13
    private static $icon = 'userforms/images/sitetree_icon.png';
0 ignored issues
show
Unused Code introduced by
The property $icon is not used and could be removed.

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

Loading history...
14
15
    /**
16
     * @var string
17
     */
18
    private static $description = 'Adds a customizable form.';
0 ignored issues
show
Unused Code introduced by
The property $description is not used and could be removed.

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

Loading history...
19
20
    /**
21
     * @var string Required Identifier
22
     */
23
    private static $required_identifier = null;
0 ignored issues
show
Unused Code introduced by
The property $required_identifier is not used and could be removed.

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

Loading history...
24
25
    /**
26
     * @var string
27
     */
28
    private static $email_template_directory = 'userforms/templates/email/';
0 ignored issues
show
Unused Code introduced by
The property $email_template_directory is not used and could be removed.

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

Loading history...
29
30
    /**
31
     * Should this module automatically upgrade on dev/build?
32
     *
33
     * @config
34
     * @var bool
35
     */
36
    private static $upgrade_on_build = true;
0 ignored issues
show
Unused Code introduced by
The property $upgrade_on_build is not used and could be removed.

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

Loading history...
37
38
    /**
39
     * Built in extensions required by this page
40
     * @config
41
     * @var array
42
     */
43
    private static $extensions = array(
0 ignored issues
show
Unused Code introduced by
The property $extensions is not used and could be removed.

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

Loading history...
44
        'UserFormFieldEditorExtension'
45
    );
46
47
    /**
48
     * @var array Fields on the user defined form page.
49
     */
50
    private static $db = array(
0 ignored issues
show
Unused Code introduced by
The property $db is not used and could be removed.

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

Loading history...
51
        "SubmitButtonText" => "Varchar",
52
        "ClearButtonText" => "Varchar",
53
        "OnCompleteMessage" => "HTMLText",
54
        "ShowClearButton" => "Boolean",
55
        'DisableSaveSubmissions' => 'Boolean',
56
        'EnableLiveValidation' => 'Boolean',
57
        'HideFieldLabels' => 'Boolean',
58
        'DisplayErrorMessagesAtTop' => 'Boolean',
59
        'DisableAuthenicatedFinishAction' => 'Boolean',
60
        'DisableCsrfSecurityToken' => 'Boolean'
61
    );
62
63
    /**
64
     * @var array Default values of variables when this page is created
65
     */
66
    private static $defaults = array(
0 ignored issues
show
Unused Code introduced by
The property $defaults is not used and could be removed.

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

Loading history...
67
        'Content' => '$UserDefinedForm',
68
        'DisableSaveSubmissions' => 0,
69
        'OnCompleteMessage' => '<p>Thanks, we\'ve received your submission.</p>'
70
    );
71
72
    /**
73
     * @var array
74
     */
75
    private static $has_many = array(
0 ignored issues
show
Unused Code introduced by
The property $has_many is not used and could be removed.

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

Loading history...
76
        "Submissions" => "SubmittedForm",
77
        "EmailRecipients" => "UserDefinedForm_EmailRecipient"
78
    );
79
80
    /**
81
     * @var array
82
     * @config
83
     */
84
    private static $casting = array(
0 ignored issues
show
Unused Code introduced by
The property $casting is not used and could be removed.

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

Loading history...
85
        'ErrorContainerID' => 'Text'
86
    );
87
88
    /**
89
     * Error container selector which matches the element for grouped messages
90
     *
91
     * @var string
92
     * @config
93
     */
94
    private static $error_container_id = 'error-container';
0 ignored issues
show
Unused Code introduced by
The property $error_container_id is not used and could be removed.

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

Loading history...
95
96
    /**
97
     * The configuration used to determine whether a confirmation message is to
98
     * appear when navigating away from a partially completed form.
99
     *
100
     * @var boolean
101
     * @config
102
     */
103
    private static $enable_are_you_sure = true;
0 ignored issues
show
Unused Code introduced by
The property $enable_are_you_sure is not used and could be removed.

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

Loading history...
104
105
    /**
106
     * @var bool
107
     * @config
108
     */
109
    private static $recipients_warning_enabled = false;
0 ignored issues
show
Unused Code introduced by
The property $recipients_warning_enabled is not used and could be removed.

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

Loading history...
110
111
    /**
112
     * Temporary storage of field ids when the form is duplicated.
113
     * Example layout: array('EditableCheckbox3' => 'EditableCheckbox14')
114
     * @var array
115
     */
116
    protected $fieldsFromTo = array();
117
118
    /**
119
     * @return FieldList
120
     */
121 1
     public function getCMSFields()
122 1
     {
123 1
         Requirements::css(USERFORMS_DIR . '/css/UserForm_cms.css');
124
125 1
         $self = $this;
126
127
         $this->beforeUpdateCMSFields(function ($fields) use ($self) {
128
129
            // define tabs
130 1
            $fields->findOrMakeTab('Root.FormOptions', _t('UserDefinedForm.CONFIGURATION', 'Configuration'));
131 1
            $fields->findOrMakeTab('Root.Recipients', _t('UserDefinedForm.RECIPIENTS', 'Recipients'));
132 1
            $fields->findOrMakeTab('Root.Submissions', _t('UserDefinedForm.SUBMISSIONS', 'Submissions'));
133
134
            // text to show on complete
135 1
            $onCompleteFieldSet = new CompositeField(
136 1
                $label = new LabelField('OnCompleteMessageLabel', _t('UserDefinedForm.ONCOMPLETELABEL', 'Show on completion')),
137 1
                $editor = new HtmlEditorField('OnCompleteMessage', '', _t('UserDefinedForm.ONCOMPLETEMESSAGE', $self->OnCompleteMessage))
138 1
            );
139
140 1
            $onCompleteFieldSet->addExtraClass('field');
141
142 1
            $editor->setRows(3);
143 1
            $label->addExtraClass('left');
144
145
            // Define config for email recipients
146 1
            $emailRecipientsConfig = GridFieldConfig_RecordEditor::create(10);
147 1
            $emailRecipientsConfig->getComponentByType('GridFieldAddNewButton')
148 1
                ->setButtonName(
149 1
                    _t('UserDefinedForm.ADDEMAILRECIPIENT', 'Add Email Recipient')
150 1
                );
151
152
            // who do we email on submission
153 1
            $emailRecipients = new GridField(
154 1
                'EmailRecipients',
155 1
                _t('UserDefinedForm.EMAILRECIPIENTS', 'Email Recipients'),
156 1
                $self->EmailRecipients(),
157
                $emailRecipientsConfig
158 1
            );
159
            $emailRecipients
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface GridFieldComponent as the method setItemRequestClass() does only exist in the following implementations of said interface: GridFieldAddNewMultiClass, GridFieldDetailForm.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

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