Completed
Pull Request — master (#624)
by Sean
16:58
created

UserDefinedForm_Controller::getMergeFieldsMap()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
crap 2
1
<?php
2
3
/**
4
 * @package userforms
5
 */
6
7
class UserDefinedForm extends Page
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

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

namespace YourVendor;

class YourClass { }

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

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

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

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

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

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

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

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

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

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

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

Loading history...
37
38
    /**
39
     * Set this to true to disable automatic inclusion of CSS files
40
     * @config
41
     * @var bool
42
     */
43
    private static $block_default_userforms_css = false;
0 ignored issues
show
Unused Code introduced by
The property $block_default_userforms_css is not used and could be removed.

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

Loading history...
44
45
    /**
46
     * Set this to true to disable automatic inclusion of JavaScript files
47
     * @config
48
     * @var bool
49
     */
50
    private static $block_default_userforms_js = false;
0 ignored issues
show
Unused Code introduced by
The property $block_default_userforms_js is not used and could be removed.

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

Loading history...
51
52
    /**
53
     * Built in extensions required by this page
54
     * @config
55
     * @var array
56
     */
57
    private static $extensions = array(
0 ignored issues
show
Unused Code introduced by
The property $extensions is not used and could be removed.

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

Loading history...
58
        'UserFormFieldEditorExtension'
59
    );
60
61
    /**
62
     * @var array Fields on the user defined form page.
63
     */
64
    private static $db = array(
0 ignored issues
show
Unused Code introduced by
The property $db is not used and could be removed.

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

Loading history...
65
        "SubmitButtonText" => "Varchar",
66
        "ClearButtonText" => "Varchar",
67
        "OnCompleteMessage" => "HTMLText",
68
        "ShowClearButton" => "Boolean",
69
        'DisableSaveSubmissions' => 'Boolean',
70
        'EnableLiveValidation' => 'Boolean',
71
        'DisplayErrorMessagesAtTop' => 'Boolean',
72
        'DisableAuthenicatedFinishAction' => 'Boolean',
73
        'DisableCsrfSecurityToken' => 'Boolean'
74
    );
75
76
    /**
77
     * @var array Default values of variables when this page is created
78
     */
79
    private static $defaults = array(
0 ignored issues
show
Unused Code introduced by
The property $defaults is not used and could be removed.

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

Loading history...
80
        'Content' => '$UserDefinedForm',
81
        'DisableSaveSubmissions' => 0,
82
        'OnCompleteMessage' => '<p>Thanks, we\'ve received your submission.</p>'
83
    );
84
85
    /**
86
     * @var array
87
     */
88
    private static $has_many = array(
0 ignored issues
show
Unused Code introduced by
The property $has_many is not used and could be removed.

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

Loading history...
89
        "Submissions" => "SubmittedForm",
90
        "EmailRecipients" => "UserDefinedForm_EmailRecipient"
91
    );
92
93
    /**
94
     * @var array
95
     * @config
96
     */
97
    private static $casting = array(
0 ignored issues
show
Unused Code introduced by
The property $casting is not used and could be removed.

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

Loading history...
98
        'ErrorContainerID' => 'Text'
99
    );
100
101
    /**
102
     * Error container selector which matches the element for grouped messages
103
     *
104
     * @var string
105
     * @config
106
     */
107
    private static $error_container_id = 'error-container';
0 ignored issues
show
Unused Code introduced by
The property $error_container_id is not used and could be removed.

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

Loading history...
108
109
    /**
110
     * The configuration used to determine whether a confirmation message is to
111
     * appear when navigating away from a partially completed form.
112
     *
113
     * @var boolean
114
     * @config
115
     */
116
    private static $enable_are_you_sure = true;
0 ignored issues
show
Unused Code introduced by
The property $enable_are_you_sure is not used and could be removed.

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

Loading history...
117
118
    /**
119
     * @var bool
120
     * @config
121
     */
122
    private static $recipients_warning_enabled = false;
0 ignored issues
show
Unused Code introduced by
The property $recipients_warning_enabled is not used and could be removed.

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

Loading history...
123
124
    /**
125
     * Temporary storage of field ids when the form is duplicated.
126
     * Example layout: array('EditableCheckbox3' => 'EditableCheckbox14')
127
     * @var array
128
     */
129
    protected $fieldsFromTo = array();
130
131
    /**
132
     * @return FieldList
133
     */
134 2
     public function getCMSFields()
135
     {
136 2
         Requirements::css(USERFORMS_DIR . '/css/UserForm_cms.css');
137
138 2
         $self = $this;
139
140
         $this->beforeUpdateCMSFields(function ($fields) use ($self) {
141
142
            // define tabs
143 2
            $fields->findOrMakeTab('Root.FormOptions', _t('UserDefinedForm.CONFIGURATION', 'Configuration'));
144 2
            $fields->findOrMakeTab('Root.Recipients', _t('UserDefinedForm.RECIPIENTS', 'Recipients'));
145 2
            $fields->findOrMakeTab('Root.Submissions', _t('UserDefinedForm.SUBMISSIONS', 'Submissions'));
146
147
            // text to show on complete
148 2
            $onCompleteFieldSet = new CompositeField(
149 2
                $label = new LabelField('OnCompleteMessageLabel', _t('UserDefinedForm.ONCOMPLETELABEL', 'Show on completion')),
150 2
                $editor = new HtmlEditorField('OnCompleteMessage', '', _t('UserDefinedForm.ONCOMPLETEMESSAGE', $self->OnCompleteMessage))
151
            );
152
153 2
            $onCompleteFieldSet->addExtraClass('field');
154
155 2
            $editor->setRows(3);
156 2
            $label->addExtraClass('left');
157
158
            // Define config for email recipients
159 2
            $emailRecipientsConfig = GridFieldConfig_RecordEditor::create(10);
160 2
            $emailRecipientsConfig->getComponentByType('GridFieldAddNewButton')
161 2
                ->setButtonName(
162 2
                    _t('UserDefinedForm.ADDEMAILRECIPIENT', 'Add Email Recipient')
163
                );
164
165
            // who do we email on submission
166 2
            $emailRecipients = new GridField(
167 2
                'EmailRecipients',
168 2
                _t('UserDefinedForm.EMAILRECIPIENTS', 'Email Recipients'),
169 2
                $self->EmailRecipients(),
170 2
                $emailRecipientsConfig
171
            );
172
            $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...
173 2
                ->getConfig()
174 2
                ->getComponentByType('GridFieldDetailForm')
175 2
                ->setItemRequestClass('UserFormRecipientItemRequest');
176
177 2
            $fields->addFieldsToTab('Root.FormOptions', $onCompleteFieldSet);
178 2
            $fields->addFieldToTab('Root.Recipients', $emailRecipients);
179 2
            $fields->addFieldsToTab('Root.FormOptions', $self->getFormOptions());
180
181
182
            // view the submissions
183 2
            $submissions = new GridField(
184 2
                'Submissions',
185 2
                _t('UserDefinedForm.SUBMISSIONS', 'Submissions'),
186 2
                 $self->Submissions()->sort('Created', 'DESC')
187
            );
188
189
            // make sure a numeric not a empty string is checked against this int column for SQL server
190 2
            $parentID = (!empty($self->ID)) ? (int) $self->ID : 0;
191
192
            // get a list of all field names and values used for print and export CSV views of the GridField below.
193
            $columnSQL = <<<SQL
194
SELECT "SubmittedFormField"."Name" as "Name", COALESCE("EditableFormField"."Title", "SubmittedFormField"."Title") as "Title", COALESCE("EditableFormField"."Sort", 999) AS "Sort"
195
FROM "SubmittedFormField"
196
LEFT JOIN "SubmittedForm" ON "SubmittedForm"."ID" = "SubmittedFormField"."ParentID"
197 2
LEFT JOIN "EditableFormField" ON "EditableFormField"."Name" = "SubmittedFormField"."Name" AND "EditableFormField"."ParentID" = '$parentID'
198 2
WHERE "SubmittedForm"."ParentID" = '$parentID'
199
ORDER BY "Sort", "Title"
200
SQL;
201
            // Sanitise periods in title
202 2
            $columns = array();
203 2
            foreach (DB::query($columnSQL)->map() as $name => $title) {
204
                $columns[$name] = trim(strtr($title, '.', ' '));
205
            }
206
207 2
            $config = new GridFieldConfig();
208 2
            $config->addComponent(new GridFieldToolbarHeader());
209 2
            $config->addComponent($sort = new GridFieldSortableHeader());
210 2
            $config->addComponent($filter = new UserFormsGridFieldFilterHeader());
211 2
            $config->addComponent(new GridFieldDataColumns());
212 2
            $config->addComponent(new GridFieldEditButton());
213 2
            $config->addComponent(new GridFieldDeleteAction());
214 2
            $config->addComponent(new GridFieldPageCount('toolbar-header-right'));
215 2
            $config->addComponent($pagination = new GridFieldPaginator(25));
216 2
            $config->addComponent(new GridFieldDetailForm());
217 2
            $config->addComponent(new GridFieldButtonRow('after'));
218 2
            $config->addComponent($export = new GridFieldExportButton('buttons-after-left'));
219 2
            $config->addComponent($print = new GridFieldPrintButton('buttons-after-left'));
220
221
            // show user form items in the summary tab
222
            $summaryarray = array(
223 2
                'ID' => 'ID',
224
                'Created' => 'Created',
225
                'LastEdited' => 'Last Edited'
226
            );
227 2
            foreach(EditableFormField::get()->filter(array("ParentID" => $parentID)) as $eff) {
228 2
                if($eff->ShowInSummary) {
229 1
                    $summaryarray[$eff->Name] = $eff->Title ?: $eff->Name;
230
                }
231
            }
232
233 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...
234
235
            /**
236
             * Support for {@link https://github.com/colymba/GridFieldBulkEditingTools}
237
             */
238 2
            if (class_exists('GridFieldBulkManager')) {
239
                $config->addComponent(new GridFieldBulkManager());
240
            }
241
242 2
            $sort->setThrowExceptionOnBadDataType(false);
243 2
            $filter->setThrowExceptionOnBadDataType(false);
244 2
            $pagination->setThrowExceptionOnBadDataType(false);
245
246
            // attach every column to the print view form
247 2
            $columns['Created'] = 'Created';
248 2
            $filter->setColumns($columns);
249
250
            // print configuration
251
252 2
            $print->setPrintHasHeader(true);
253 2
            $print->setPrintColumns($columns);
254
255
            // export configuration
256 2
            $export->setCsvHasHeader(true);
257 2
            $export->setExportColumns($columns);
258
259 2
            $submissions->setConfig($config);
260 2
            $fields->addFieldToTab('Root.Submissions', $submissions);
261 2
            $fields->addFieldToTab('Root.FormOptions', new CheckboxField('DisableSaveSubmissions', _t('UserDefinedForm.SAVESUBMISSIONS', 'Disable Saving Submissions to Server')));
262
263 2
        });
264
265 2
         $fields = parent::getCMSFields();
266
267 2
         if ($this->EmailRecipients()->Count() == 0 && static::config()->recipients_warning_enabled) {
268
             $fields->addFieldToTab("Root.Main", new LiteralField("EmailRecipientsWarning",
269
                "<p class=\"message warning\">" . _t("UserDefinedForm.NORECIPIENTS",
270
                "Warning: You have not configured any recipients. Form submissions may be missed.")
271
                . "</p>"), "Title");
272
         }
273
274 2
         return $fields;
275
     }
276
277
    /**
278
     * Allow overriding the EmailRecipients on a {@link DataExtension}
279
     * so you can customise who receives an email.
280
     * Converts the RelationList to an ArrayList so that manipulation
281
     * of the original source data isn't possible.
282
     *
283
     * @return ArrayList
284
     */
285 3
    public function FilteredEmailRecipients($data = null, $form = null)
286
    {
287 3
        $recipients = new ArrayList($this->EmailRecipients()->toArray());
288
289
        // Filter by rules
290
        $recipients = $recipients->filterByCallback(function ($recipient) use ($data, $form) {
291
            /** @var UserDefinedForm_EmailRecipient $recipient */
292 2
            return $recipient->canSend($data, $form);
293 3
        });
294
295 3
        $this->extend('updateFilteredEmailRecipients', $recipients, $data, $form);
296
297 3
        return $recipients;
298
    }
299
300
    /**
301
     * Custom options for the form. You can extend the built in options by
302
     * using {@link updateFormOptions()}
303
     *
304
     * @return FieldList
305
     */
306 3
    public function getFormOptions()
307
    {
308 3
        $submit = ($this->SubmitButtonText) ? $this->SubmitButtonText : _t('UserDefinedForm.SUBMITBUTTON', 'Submit');
309 3
        $clear = ($this->ClearButtonText) ? $this->ClearButtonText : _t('UserDefinedForm.CLEARBUTTON', 'Clear');
310
311 3
        $options = new FieldList(
312 3
            new TextField("SubmitButtonText", _t('UserDefinedForm.TEXTONSUBMIT', 'Text on submit button:'), $submit),
313 3
            new TextField("ClearButtonText", _t('UserDefinedForm.TEXTONCLEAR', 'Text on clear button:'), $clear),
314 3
            new CheckboxField("ShowClearButton", _t('UserDefinedForm.SHOWCLEARFORM', 'Show Clear Form Button'), $this->ShowClearButton),
315 3
            new CheckboxField("EnableLiveValidation", _t('UserDefinedForm.ENABLELIVEVALIDATION', 'Enable live validation')),
316 3
            new CheckboxField("DisplayErrorMessagesAtTop", _t('UserDefinedForm.DISPLAYERRORMESSAGESATTOP', 'Display error messages above the form?')),
317 3
            new CheckboxField('DisableCsrfSecurityToken', _t('UserDefinedForm.DISABLECSRFSECURITYTOKEN', 'Disable CSRF Token')),
318 3
            new CheckboxField('DisableAuthenicatedFinishAction', _t('UserDefinedForm.DISABLEAUTHENICATEDFINISHACTION', 'Disable Authentication on finish action'))
319
        );
320
321 3
        $this->extend('updateFormOptions', $options);
322
323 3
        return $options;
324
    }
325
326
    /**
327
     * Get the HTML id of the error container displayed above the form.
328
     *
329
     * @return string
330
     */
331
    public function getErrorContainerID()
332
    {
333
        return $this->config()->error_container_id;
334
    }
335
336
    public function requireDefaultRecords()
337
    {
338
        parent::requireDefaultRecords();
339
340
        if (!$this->config()->upgrade_on_build) {
341
            return;
342
        }
343
344
        // Perform migrations
345
        Injector::inst()
346
            ->create('UserFormsUpgradeService')
347
            ->setQuiet(true)
348
            ->run();
349
350
        DB::alteration_message('Migrated userforms', 'changed');
351
    }
352
353
354
    /**
355
     * Validate formfields
356
     */
357
    public function getCMSValidator()
358
    {
359
        return new UserFormValidator();
360
    }
361
}
362
363
/**
364
 * Controller for the {@link UserDefinedForm} page type.
365
 *
366
 * @package userforms
367
 */
368
369
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...
370
{
371
372
    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...
373
374
    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...
375
        'index',
376
        'ping',
377
        'Form',
378
        'finished'
379
    );
380
381 5
    public function init()
382
    {
383 5
        parent::init();
384
385 5
        $page = $this->data();
386
387
        // load the css
388 5
        if (!$page->config()->block_default_userforms_css) {
389 5
            Requirements::css(USERFORMS_DIR . '/css/UserForm.css');
390
        }
391
392
        // load the jquery
393 5
        if (!$page->config()->block_default_userforms_js) {
394 5
        $lang = i18n::get_lang_from_locale(i18n::get_locale());
395 5
        Requirements::javascript(FRAMEWORK_DIR .'/thirdparty/jquery/jquery.js');
396 5
        Requirements::javascript(USERFORMS_DIR . '/thirdparty/jquery-validate/jquery.validate.min.js');
397 5
        Requirements::add_i18n_javascript(USERFORMS_DIR . '/javascript/lang');
398 5
        Requirements::javascript(USERFORMS_DIR . '/javascript/UserForm.js');
399
400 5
        Requirements::javascript(
401 5
            USERFORMS_DIR . "/thirdparty/jquery-validate/localization/messages_{$lang}.min.js"
402
        );
403 5
        Requirements::javascript(
404 5
            USERFORMS_DIR . "/thirdparty/jquery-validate/localization/methods_{$lang}.min.js"
405
        );
406
407
        // Bind a confirmation message when navigating away from a partially completed form.
408 5
        if ($page::config()->enable_are_you_sure) {
409 5
            Requirements::javascript(USERFORMS_DIR . '/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js');
410
        }
411
    }
412 5
    }
413
414
    /**
415
     * Using $UserDefinedForm in the Content area of the page shows
416
     * where the form should be rendered into. If it does not exist
417
     * then default back to $Form.
418
     *
419
     * @return array
420
     */
421 6
    public function index()
422
    {
423 6
        if ($this->Content && $form = $this->Form()) {
424 6
            $hasLocation = stristr($this->Content, '$UserDefinedForm');
425 6
            if ($hasLocation) {
426 5
                $content = preg_replace('/(<p[^>]*>)?\\$UserDefinedForm(<\\/p>)?/i', $form->forTemplate(), $this->Content);
427
                return array(
428 5
                    'Content' => DBField::create_field('HTMLText', $content),
429 5
                    'Form' => ""
430
                );
431
            }
432
        }
433
434
        return array(
435 1
            'Content' => DBField::create_field('HTMLText', $this->Content),
436 1
            'Form' => $this->Form()
437
        );
438
    }
439
440
    /**
441
     * Keep the session alive for the user.
442
     *
443
     * @return int
444
     */
445
    public function ping()
446
    {
447
        return 1;
448
    }
449
450
    /**
451
     * Get the form for the page. Form can be modified by calling {@link updateForm()}
452
     * on a UserDefinedForm extension.
453
     *
454
     * @return Forms
455
     */
456 9
    public function Form()
457
    {
458 9
        $form = UserForm::create($this, 'Form_' . $this->ID);
459 9
        $form->setFormAction(Controller::join_links($this->Link(), 'Form'));
460 9
        $this->generateConditionalJavascript();
461 9
        return $form;
462
    }
463
464
    /**
465
     * Generate the javascript for the conditional field show / hiding logic.
466
     *
467
     * @return void
468
     */
469 9
    public function generateConditionalJavascript()
470
    {
471 9
        $default = "";
0 ignored issues
show
Unused Code introduced by
$default is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
472 9
        $rules = "";
473
474 9
        $watch = array();
475
476 9
        if ($this->Fields()) {
477
            /** @var EditableFormField $field */
478 9
            foreach ($this->Fields() as $field) {
479 8
                if ($result = $field->formatDisplayRules()) {
480
                    $watch[] = $result;
481
                }
482
            }
483
        }
484 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...
485
            $rules .= $this->buildWatchJS($watch);
486
                }
487
488
        // Only add customScript if $default or $rules is defined
489 9
        if ($rules) {
490
            Requirements::customScript(<<<JS
491
                (function($) {
492
                    $(document).ready(function() {
493
                        {$rules}
494
                    });
495
                })(jQuery);
496
JS
497
, 'UserFormsConditional');
498
        }
499 9
    }
500
501
    /**
502
     * Process the form that is submitted through the site
503
     *
504
     * {@see UserForm::validate()} for validation step prior to processing
505
     *
506
     * @param array $data
507
     * @param Form $form
508
     *
509
     * @return Redirection
510
     */
511 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...
512
    {
513 2
        $submittedForm = Object::create('SubmittedForm');
514 2
        $submittedForm->SubmittedByID = ($id = Member::currentUserID()) ? $id : 0;
515 2
        $submittedForm->ParentID = $this->ID;
516
517
        // if saving is not disabled save now to generate the ID
518 2
        if (!$this->DisableSaveSubmissions) {
519 2
            $submittedForm->write();
520
        }
521
522 2
        $attachments = array();
523 2
        $submittedFields = new ArrayList();
524
525 2
        foreach ($this->Fields() as $field) {
526 2
            if (!$field->showInReports()) {
527 2
                continue;
528
            }
529
530 2
            $submittedField = $field->getSubmittedFormField();
531 2
            $submittedField->ParentID = $submittedForm->ID;
532 2
            $submittedField->Name = $field->Name;
533 2
            $submittedField->Title = $field->getField('Title');
534
535
            // save the value from the data
536 2
            if ($field->hasMethod('getValueFromData')) {
537
                $submittedField->Value = $field->getValueFromData($data);
538
            } else {
539 2
                if (isset($data[$field->Name])) {
540 2
                    $submittedField->Value = $data[$field->Name];
541
                }
542
            }
543
544 2
            if (!empty($data[$field->Name])) {
545 2
                if (in_array("EditableFileField", $field->getClassAncestry())) {
546
                    if (!empty($_FILES[$field->Name]['name'])) {
547
                        $foldername = $field->getFormField()->getFolderName();
548
549
                        // create the file from post data
550
                        $upload = new Upload();
551
                        $file = new File();
552
                        $file->ShowInSearch = 0;
553
                        try {
554
                            $upload->loadIntoFile($_FILES[$field->Name], $file, $foldername);
555
                        } catch (ValidationException $e) {
556
                            $validationResult = $e->getResult();
557
                            $form->addErrorMessage($field->Name, $validationResult->message(), 'bad');
558
                            Controller::curr()->redirectBack();
559
                            return;
560
                        }
561
562
                        // write file to form field
563
                        $submittedField->UploadedFileID = $file->ID;
564
565
                        // attach a file only if lower than 1MB
566
                        if ($file->getAbsoluteSize() < 1024*1024*1) {
567
                            $attachments[] = $file;
568
                        }
569
                    }
570
                }
571
            }
572
573 2
            $submittedField->extend('onPopulationFromField', $field);
574
575 2
            if (!$this->DisableSaveSubmissions) {
576 2
                $submittedField->write();
577
            }
578
579 2
            $submittedFields->push($submittedField);
580
        }
581
582
        $emailData = array(
583 2
            "Sender" => Member::currentUser(),
584 2
            "Fields" => $submittedFields
585
        );
586
587 2
        $this->extend('updateEmailData', $emailData, $attachments);
588
589
        // email users on submit.
590 2
        if ($recipients = $this->FilteredEmailRecipients($data, $form)) {
591 2
            foreach ($recipients as $recipient) {
592 1
                $email = new UserFormRecipientEmail($submittedFields);
593 1
                $mergeFields = $this->getMergeFieldsMap($emailData['Fields']);
594
595 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...
596
                    foreach ($attachments as $file) {
597
                        if ($file->ID != 0) {
598
                            $email->attachFile(
599
                                $file->Filename,
600
                                $file->Filename,
601
                                HTTP::get_mime_type($file->Filename)
602
                            );
603
                        }
604
                    }
605
                }
606
607 1
                $parsedBody = SSViewer::execute_string($recipient->getEmailBodyContent(), $mergeFields);
608
609 1
                if (!$recipient->SendPlain && $recipient->emailTemplateExists()) {
610
                    $email->setTemplate($recipient->EmailTemplate);
611
                }
612
613 1
                $email->populateTemplate($recipient);
614 1
                $email->populateTemplate($emailData);
615 1
                $email->setFrom($recipient->EmailFrom);
616 1
                $email->setBody($parsedBody);
617 1
                $email->setTo($recipient->EmailAddress);
618 1
                $email->setSubject($recipient->EmailSubject);
619
620 1
                if ($recipient->EmailReplyTo) {
621
                    $email->setReplyTo($recipient->EmailReplyTo);
622
                }
623
624
                // check to see if they are a dynamic reply to. eg based on a email field a user selected
625 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...
626 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailFromField()->Name);
627
628 1
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
629
                        $email->setReplyTo($submittedFormField->Value);
630
                    }
631
                }
632
                // check to see if they are a dynamic reciever eg based on a dropdown field a user selected
633 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...
634 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailToField()->Name);
635
636 1
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
637
                        $email->setTo($submittedFormField->Value);
638
                    }
639
                }
640
641
                // check to see if there is a dynamic subject
642 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...
643 1
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailSubjectField()->Name);
644
645 1
                    if ($submittedFormField && trim($submittedFormField->Value)) {
646
                        $email->setSubject($submittedFormField->Value);
647
                    }
648
                }
649
650 1
                $this->extend('updateEmail', $email, $recipient, $emailData);
651
652 1
                if ($recipient->SendPlain) {
653 1
                    $body = strip_tags($recipient->getEmailBodyContent()) . "\n";
654 1
                    if (isset($emailData['Fields']) && !$recipient->HideFormData) {
655 1
                        foreach ($emailData['Fields'] as $Field) {
656 1
                            $body .= $Field->Title .': '. $Field->Value ." \n";
657
                        }
658
                    }
659
660 1
                    $email->setBody($body);
661 1
                    $email->sendPlain();
662
                } else {
663 1
                    $email->send();
664
                }
665
            }
666
        }
667
668 2
        $submittedForm->extend('updateAfterProcess');
669
670 2
        Session::clear("FormInfo.{$form->FormName()}.errors");
671 2
        Session::clear("FormInfo.{$form->FormName()}.data");
672
673 2
        $referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
674
675
        // set a session variable from the security ID to stop people accessing
676
        // the finished method directly.
677 2
        if (!$this->DisableAuthenicatedFinishAction) {
678 2
            if (isset($data['SecurityID'])) {
679
                Session::set('FormProcessed', $data['SecurityID']);
680
            } else {
681
                // if the form has had tokens disabled we still need to set FormProcessed
682
                // to allow us to get through the finshed method
683 2
                if (!$this->Form()->getSecurityToken()->isEnabled()) {
684 2
                    $randNum = rand(1, 1000);
685 2
                    $randHash = md5($randNum);
686 2
                    Session::set('FormProcessed', $randHash);
687 2
                    Session::set('FormProcessedNum', $randNum);
688
                }
689
            }
690
        }
691
692 2
        if (!$this->DisableSaveSubmissions) {
693 2
            Session::set('userformssubmission'. $this->ID, $submittedForm->ID);
694
        }
695
696 2
        return $this->redirect($this->Link('finished') . $referrer . $this->config()->finished_anchor);
697
    }
698
699
    /**
700
     * Allows the use of field values in email body.
701
     *
702
     * @param ArrayList fields
703
     * @return ArrayData
704
     */
705 1
    private function getMergeFieldsMap($fields = array())
706
    {
707 1
        $data = new ArrayData(array());
708
709 1
        foreach ($fields as $field) {
710 1
            $data->setField($field->Name, DBField::create_field('Text', $field->Value));
711
        }
712
713 1
        return $data;
714
    }
715
716
    /**
717
     * This action handles rendering the "finished" message, which is
718
     * customizable by editing the ReceivedFormSubmission template.
719
     *
720
     * @return ViewableData
721
     */
722 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...
723
    {
724 3
        $submission = Session::get('userformssubmission'. $this->ID);
725
726 3
        if ($submission) {
727 1
            $submission = SubmittedForm::get()->byId($submission);
728
        }
729
730 3
        $referrer = isset($_GET['referrer']) ? urldecode($_GET['referrer']) : null;
731
732 3
        if (!$this->DisableAuthenicatedFinishAction) {
733 3
            $formProcessed = Session::get('FormProcessed');
734
735 3
            if (!isset($formProcessed)) {
736 1
                return $this->redirect($this->Link() . $referrer);
737
            } else {
738 2
                $securityID = Session::get('SecurityID');
739
                // make sure the session matches the SecurityID and is not left over from another form
740 2
                if ($formProcessed != $securityID) {
741
                    // they may have disabled tokens on the form
742 1
                    $securityID = md5(Session::get('FormProcessedNum'));
743 1
                    if ($formProcessed != $securityID) {
744
                        return $this->redirect($this->Link() . $referrer);
745
                    }
746
                }
747
            }
748
749 2
            Session::clear('FormProcessed');
750
        }
751
752
        $data = array(
753 2
                'Submission' => $submission,
754 2
                'Link' => $referrer
755
        );
756
757 2
        $this->extend('updateReceivedFormSubmissionData', $data);
758
759 2
        return $this->customise(array(
760 2
            'Content' => $this->customise($data)->renderWith('ReceivedFormSubmission'),
761 2
            'Form' => '',
762
        ));
763
    }
764
765
    /**
766
     * Outputs the required JS from the $watch input
767
     *
768
     * @param array $watch
769
     *
770
     * @return string
771
     */
772
    protected function buildWatchJS($watch)
773
    {
774
        $result = '';
775
        foreach ($watch as $key => $rule) {
776
            $events = implode(' ', $rule['events']);
777
            $selectors = implode(', ', $rule['selectors']);
778
            $conjunction = $rule['conjunction'];
779
            $operations = implode(" {$conjunction} ", $rule['operations']);
780
            $target = $rule['targetFieldID'];
781
782
            $result .= <<<EOS
783
\n
784
    $('.userform').on('{$events}',
785
    "{$selectors}",
786
    function (){
787
        if ({$operations}) {
788
            $('{$target}').{$rule['view']};
789
        } else {
790
            $('{$target}').{$rule['opposite']};
791
        }
792
    });
793
    $("{$target}").find('.hide').removeClass('hide');
794
EOS;
795
        }
796
797
        return $result;
798
    }
799
}
800