Completed
Pull Request — master (#554)
by
unknown
18:41
created

UserDefinedForm_Controller::index()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 11
cts 11
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 3
nop 0
crap 4
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
        // load the jquery
373 5
        $lang = i18n::get_lang_from_locale(i18n::get_locale());
374 5
        Requirements::css(USERFORMS_DIR . '/css/UserForm.css');
375 5
        Requirements::javascript(FRAMEWORK_DIR .'/thirdparty/jquery/jquery.js');
376 5
        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
380 5
        Requirements::javascript(
381 5
            USERFORMS_DIR . "/thirdparty/jquery-validate/localization/messages_{$lang}.min.js"
382 5
        );
383 5
        Requirements::javascript(
384 6
            USERFORMS_DIR . "/thirdparty/jquery-validate/localization/methods_{$lang}.min.js"
385 6
        );
386 5
        if ($this->HideFieldLabels) {
387
            Requirements::javascript(USERFORMS_DIR . '/thirdparty/Placeholders.js/Placeholders.min.js');
388
        }
389
390
        // Bind a confirmation message when navigating away from a partially completed form.
391 5
        $page = $this->data();
392 5
        if ($page::config()->enable_are_you_sure) {
393 5
            Requirements::javascript(USERFORMS_DIR . '/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js');
394 5
        }
395 5
    }
396
397
    /**
398
     * Using $UserDefinedForm in the Content area of the page shows
399
     * where the form should be rendered into. If it does not exist
400
     * then default back to $Form.
401
     *
402
     * @return array
403
     */
404 6
    public function index()
405
    {
406 6
        if ($this->Content && $form = $this->Form()) {
407 6
            $hasLocation = stristr($this->Content, '$UserDefinedForm');
408 6
            if ($hasLocation) {
409 5
                $content = preg_replace('/(<p[^>]*>)?\\$UserDefinedForm(<\\/p>)?/i', $form->forTemplate(), $this->Content);
410
                return array(
411 5
                    'Content' => DBField::create_field('HTMLText', $content),
412
                    'Form' => ""
413 5
                );
414
            }
415 1
        }
416
417
        return array(
418 1
            'Content' => DBField::create_field('HTMLText', $this->Content),
419 1
            'Form' => $this->Form()
420 2
        );
421
    }
422
423
    /**
424
     * Keep the session alive for the user.
425
     *
426
     * @return int
427
     */
428
    public function ping()
429
    {
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
     * @return Forms
438
     */
439 9
    public function Form()
440
    {
441 9
        $form = UserForm::create($this);
442 9
        $this->generateConditionalJavascript();
443 9
        return $form;
444
    }
445
446
    /**
447
     * Generate the javascript for the conditional field show / hiding logic.
448
     *
449
     * @return void
450
     */
451 9
    public function generateConditionalJavascript()
452
    {
453 9
        $default = "";
454 9
        $rules = "";
455
456 9
        $watch = array();
457 9
        $watchLoad = array();
458
459 9
        if ($this->Fields()) {
460 9
            foreach ($this->Fields() as $field) {
461 8
                $holderSelector = $field->getSelectorHolder();
462
463
                // Is this Field Show by Default
464 8
                if (!$field->ShowOnLoad) {
465
                    $default .= "{$holderSelector}.hide().trigger('userform.field.hide');\n";
466
                }
467
468
                // Check for field dependencies / default
469 8
                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
                        case 'ValueGreaterThan':
535 1
                            $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
                                $expression = '$(this).parents(".field, .control-group").find("input:checked").val()!="'. $rule->FieldValue .'"';
548 1
                            } else {
549
                                $expression = '$(this).val() != "'. $rule->FieldValue .'"';
550
                            }
551
552
                            break;
553
                    }
554
555
                    if (!isset($watch[$fieldToWatch])) {
556 1
                        $watch[$fieldToWatch] = array();
557
                    }
558
559
                    $watch[$fieldToWatch][] = array(
560
                        'expression' => $expression,
561
                        'holder_selector' => $holderSelector,
562
                        'view' => $view,
563
                        'opposite' => $opposite,
564 1
                        'action' => $action
565
                    );
566
567
                    $watchLoad[$fieldToWatchOnLoad] = true;
568 8
                }
569 9
            }
570 9
        }
571
572 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...
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
                $logic = implode("\n", $logic);
601 1
                $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
});\n";
610
                }
611
            }
612
        }
613
614 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...
615
            foreach ($watchLoad as $key => $value) {
616 1
                $rules .= $key.".each(function() {
617
	$(this).data('userformConditions').call(this);\n
618
});\n";
619
            }
620
        }
621
622
        // Only add customScript if $default or $rules is defined
623 9
        if ($default  || $rules) {
624 1
            Requirements::customScript(<<<JS
625
				(function($) {
626 3
					$(document).ready(function() {
627
						$default
628
629
						$rules
630
					})
631
				})(jQuery);
632 1
JS
633
, 'UserFormsConditional');
634
        }
635 9
    }
636
637
    /**
638
     * Process the form that is submitted through the site
639
     *
640
     * {@see UserForm::validate()} for validation step prior to processing
641
     *
642
     * @param array $data
643
     * @param Form $form
644
     *
645
     * @return Redirection
646
     */
647 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...
648
    {
649 2
        $submittedForm = Object::create('SubmittedForm');
650 2
        $submittedForm->SubmittedByID = ($id = Member::currentUserID()) ? $id : 0;
651 2
        $submittedForm->ParentID = $this->ID;
652
653
        // if saving is not disabled save now to generate the ID
654 2
        if (!$this->DisableSaveSubmissions) {
655 2
            $submittedForm->write();
656 2
        }
657
658 2
        $attachments = array();
659 2
        $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 2
            $submittedField->Name = $field->Name;
669 3
            $submittedField->Title = $field->getField('Title');
670
671
            // save the value from the data
672 2
            if ($field->hasMethod('getValueFromData')) {
673
                $submittedField->Value = $field->getValueFromData($data);
674
            } else {
675 2
                if (isset($data[$field->Name])) {
676 2
                    $submittedField->Value = $data[$field->Name];
677 2
                }
678
            }
679
680 2
            if (!empty($data[$field->Name])) {
681 2
                if (in_array("EditableFileField", $field->getClassAncestry())) {
682
                    if (!empty($_FILES[$field->Name]['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
                            $form->addErrorMessage($field->Name, $validationResult->message(), 'bad');
694
                            Controller::curr()->redirectBack();
695
                            return;
696
                        }
697
698
                        // write file to form field
699
                        $submittedField->UploadedFileID = $file->ID;
700
701
                        // attach a file only if lower than 1MB
702
                        if ($file->getAbsoluteSize() < 1024*1024*1) {
703
                            $attachments[] = $file;
704
                        }
705
                    }
706
                }
707 2
            }
708
709 2
            $submittedField->extend('onPopulationFromField', $field);
710
711 2
            if (!$this->DisableSaveSubmissions) {
712 2
                $submittedField->write();
713 2
            }
714
715 2
            $submittedFields->push($submittedField);
716 2
        }
717
718
        $emailData = array(
719 2
            "Sender" => Member::currentUser(),
720
            "Fields" => $submittedFields
721 2
        );
722
723 2
        $this->extend('updateEmailData', $emailData, $attachments);
724
725
        // email users on submit.
726 2
        if ($recipients = $this->FilteredEmailRecipients($data, $form)) {
727 2
            foreach ($recipients as $recipient) {
728 1
                $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
                                $file->Filename,
736
                                $file->Filename,
737
                                HTTP::get_mime_type($file->Filename)
738
                            );
739
                        }
740
                    }
741
                }
742
                
743 1
                $parsedBody = SSViewer::execute_string($recipient->getEmailBodyContent(), $mergeFields);
744
745 1
                if (!$recipient->SendPlain && $recipient->emailTemplateExists()) {
746
                    $email->setTemplate($recipient->EmailTemplate);
747
                }
748
749 1
                $email->populateTemplate($recipient);
750 1
                $email->populateTemplate($emailData);
751 1
                $email->setFrom($recipient->EmailFrom);
752 1
                $email->setBody($parsedBody);
753 1
                $email->setTo($recipient->EmailAddress);
754 1
                $email->setSubject($recipient->EmailSubject);
755
756 1
                if ($recipient->EmailReplyTo) {
757
                    $email->setReplyTo($recipient->EmailReplyTo);
758
                }
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 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailFromField()->Name);
763
764 1
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
765
                        $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 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...
770 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailToField()->Name);
771
772 1
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
773
                        $email->setTo($submittedFormField->Value);
774
                    }
775 1
                }
776
777
                // 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
781 1
                    if ($submittedFormField && trim($submittedFormField->Value)) {
782
                        $email->setSubject($submittedFormField->Value);
783
                    }
784 1
                }
785
786 1
                $this->extend('updateEmail', $email, $recipient, $emailData);
787
788 1
                if ($recipient->SendPlain) {
789 1
                    $body = strip_tags($recipient->getEmailBodyContent()) . "\n";
790 1
                    if (isset($emailData['Fields']) && !$recipient->HideFormData) {
791 1
                        foreach ($emailData['Fields'] as $Field) {
792 1
                            $body .= $Field->Title .': '. $Field->Value ." \n";
793 1
                        }
794 1
                    }
795
796 1
                    $email->setBody($body);
797 1
                    $email->sendPlain();
798 1
                } else {
799 1
                    $email->send();
800
                }
801 2
            }
802 2
        }
803
804 2
        $submittedForm->extend('updateAfterProcess');
805
806 2
        Session::clear("FormInfo.{$form->FormName()}.errors");
807 2
        Session::clear("FormInfo.{$form->FormName()}.data");
808
809 2
        $referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
810
811
        // set a session variable from the security ID to stop people accessing
812
        // the finished method directly.
813 2
        if (!$this->DisableAuthenicatedFinishAction) {
814 2
            if (isset($data['SecurityID'])) {
815
                Session::set('FormProcessed', $data['SecurityID']);
816
            } else {
817
                // if the form has had tokens disabled we still need to set FormProcessed
818
                // to allow us to get through the finshed method
819 2
                if (!$this->Form()->getSecurityToken()->isEnabled()) {
820 2
                    $randNum = rand(1, 1000);
821 2
                    $randHash = md5($randNum);
822 2
                    Session::set('FormProcessed', $randHash);
823 2
                    Session::set('FormProcessedNum', $randNum);
824 2
                }
825
            }
826 2
        }
827
828 2
        if (!$this->DisableSaveSubmissions) {
829 2
            Session::set('userformssubmission'. $this->ID, $submittedForm->ID);
830 2
        }
831
832 2
        return $this->redirect($this->Link('finished') . $referrer . $this->config()->finished_anchor);
833
    }
834
835
    /**
836
     * Allows the use of field values in email body.
837
     *
838
     * @param ArrayList fields
839
     * @return ArrayData
840
     */
841 1
    private function getMergeFieldsMap($fields = array())
842
    {
843 1
        $data = new ArrayData(array());
844
845 1
        foreach ($fields as $field) {
846 1
            $data->setField($field->Name, DBField::create_field('Text', $field->Value));
847 1
        }
848
849 1
        return $data;
850
    }
851
852
    /**
853
     * This action handles rendering the "finished" message, which is
854
     * customizable by editing the ReceivedFormSubmission template.
855
     *
856
     * @return ViewableData
857
     */
858 3
    public function finished()
0 ignored issues
show
Coding Style introduced by
finished uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
859
    {
860 3
        $submission = Session::get('userformssubmission'. $this->ID);
861
862 3
        if ($submission) {
863 1
            $submission = SubmittedForm::get()->byId($submission);
864 1
        }
865
866 3
        $referrer = isset($_GET['referrer']) ? urldecode($_GET['referrer']) : null;
867
868 3
        if (!$this->DisableAuthenicatedFinishAction) {
869 3
            $formProcessed = Session::get('FormProcessed');
870
871 3
            if (!isset($formProcessed)) {
872 1
                return $this->redirect($this->Link() . $referrer);
873
            } else {
874 2
                $securityID = Session::get('SecurityID');
875
                // 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 1
                    $securityID = md5(Session::get('FormProcessedNum'));
879 1
                    if ($formProcessed != $securityID) {
880
                        return $this->redirect($this->Link() . $referrer);
881
                    }
882 1
                }
883
            }
884
885 2
            Session::clear('FormProcessed');
886 2
        }
887
888 2
        return $this->customise(array(
889 2
            'Content' => $this->customise(array(
890 2
                'Submission' => $submission,
891
                'Link' => $referrer
892 2
            ))->renderWith('ReceivedFormSubmission'),
893 2
            'Form' => '',
894 2
        ));
895
    }
896
}
897