Completed
Push — master ( ebbf9a...c78942 )
by Damian
35:58
created

UserDefinedForm_Controller::buildWatchJS()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 33
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 2.1922

Importance

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

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

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

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

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

Loading history...
476
        $rules = "";
477 9
478 9
        $watch = array();
479
480 9
        if ($this->Fields()) {
481 9
            /** @var EditableFormField $field */
482 8
            foreach ($this->Fields() as $field) {
483
                if ($result = $field->formatDisplayRules()) {
484
                    $watch[] = $result;
485 8
                }
486
            }
487
        }
488
        if ($watch) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $watch of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
489
            $rules .= $this->buildWatchJS($watch);
490 8
                }
491
492
        // Only add customScript if $default or $rules is defined
493
        if ($rules) {
494
            Requirements::customScript(<<<JS
495
                (function($) {
496
                    $(document).ready(function() {
497
                        {$rules}
498
                    });
499
                })(jQuery);
500
JS
501
, 'UserFormsConditional');
502
        }
503
    }
504
505
    /**
506
     * Process the form that is submitted through the site
507
     *
508
     * {@see UserForm::validate()} for validation step prior to processing
509
     *
510
     * @param array $data
511
     * @param Form $form
512
     *
513
     * @return Redirection
514
     */
515
    public function process($data, $form)
0 ignored issues
show
Coding Style introduced by
process uses the super-global variable $_FILES which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
516
    {
517
        $submittedForm = Object::create('SubmittedForm');
518
        $submittedForm->SubmittedByID = ($id = Member::currentUserID()) ? $id : 0;
519
        $submittedForm->ParentID = $this->ID;
520
521
        // if saving is not disabled save now to generate the ID
522
        if (!$this->DisableSaveSubmissions) {
523
            $submittedForm->write();
524
        }
525
526
        $attachments = array();
527
        $submittedFields = new ArrayList();
528
529
        foreach ($this->Fields() as $field) {
530
            if (!$field->showInReports()) {
531
                continue;
532
            }
533
534
            $submittedField = $field->getSubmittedFormField();
535 1
            $submittedField->ParentID = $submittedForm->ID;
536
            $submittedField->Name = $field->Name;
537
            $submittedField->Title = $field->getField('Title');
538
539
            // save the value from the data
540
            if ($field->hasMethod('getValueFromData')) {
541
                $submittedField->Value = $field->getValueFromData($data);
542
            } else {
543
                if (isset($data[$field->Name])) {
544
                    $submittedField->Value = $data[$field->Name];
545
                }
546
            }
547
548 1
            if (!empty($data[$field->Name])) {
549
                if (in_array("EditableFileField", $field->getClassAncestry())) {
550
                    if (!empty($_FILES[$field->Name]['name'])) {
551
                        $foldername = $field->getFormField()->getFolderName();
552
553
                        // create the file from post data
554
                        $upload = new Upload();
555
                        $file = new File();
556 1
                        $file->ShowInSearch = 0;
557
                        try {
558
                            $upload->loadIntoFile($_FILES[$field->Name], $file, $foldername);
559
                        } catch (ValidationException $e) {
560
                            $validationResult = $e->getResult();
561
                            $form->addErrorMessage($field->Name, $validationResult->message(), 'bad');
562
                            Controller::curr()->redirectBack();
563
                            return;
564 1
                        }
565
566
                        // write file to form field
567
                        $submittedField->UploadedFileID = $file->ID;
568
569
                        // attach a file only if lower than 1MB
570
                        if ($file->getAbsoluteSize() < 1024*1024*1) {
571
                            $attachments[] = $file;
572
                        }
573
                    }
574
                }
575
            }
576
577
            $submittedField->extend('onPopulationFromField', $field);
578
579
            if (!$this->DisableSaveSubmissions) {
580
                $submittedField->write();
581
            }
582
583
            $submittedFields->push($submittedField);
584
        }
585
586
        $emailData = array(
587
            "Sender" => Member::currentUser(),
588
            "Fields" => $submittedFields
589 8
        );
590 9
591 9
        $this->extend('updateEmailData', $emailData, $attachments);
592
593 9
        // email users on submit.
594
        if ($recipients = $this->FilteredEmailRecipients($data, $form)) {
595
            foreach ($recipients as $recipient) {
596
                $email = new UserFormRecipientEmail($submittedFields);
597
                $mergeFields = $this->getMergeFieldsMap($emailData['Fields']);
598
599
                if ($attachments) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attachments of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
600
                    foreach ($attachments as $file) {
601
                        if ($file->ID != 0) {
602
                            $email->attachFile(
603
                                $file->Filename,
604
                                $file->Filename,
605
                                HTTP::get_mime_type($file->Filename)
606
                            );
607
                        }
608
                    }
609
                }
610
611
                $parsedBody = SSViewer::execute_string($recipient->getEmailBodyContent(), $mergeFields);
612
613
                if (!$recipient->SendPlain && $recipient->emailTemplateExists()) {
614
                    $email->setTemplate($recipient->EmailTemplate);
615
                }
616 1
617
                $email->populateTemplate($recipient);
618
                $email->populateTemplate($emailData);
619
                $email->setFrom($recipient->EmailFrom);
620
                $email->setBody($parsedBody);
621
                $email->setTo($recipient->EmailAddress);
622
                $email->setSubject($recipient->EmailSubject);
623
624 1
                if ($recipient->EmailReplyTo) {
625
                    $email->setReplyTo($recipient->EmailReplyTo);
626 3
                }
627
628
                // check to see if they are a dynamic reply to. eg based on a email field a user selected
629 View Code Duplication
                if ($recipient->SendEmailFromField()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
630
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailFromField()->Name);
631
632 1
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
633
                        $email->setReplyTo($submittedFormField->Value);
634
                    }
635 9
                }
636
                // check to see if they are a dynamic reciever eg based on a dropdown field a user selected
637 View Code Duplication
                if ($recipient->SendEmailToField()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
638
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailToField()->Name);
639
640
                    if ($submittedFormField && is_string($submittedFormField->Value)) {
641
                        $email->setTo($submittedFormField->Value);
642
                    }
643
                }
644 9
645
                // check to see if there is a dynamic subject
646 View Code Duplication
                if ($recipient->SendEmailSubjectField()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
647
                    $submittedFormField = $submittedFields->find('Name', $recipient->SendEmailSubjectField()->Name);
648
649
                    if ($submittedFormField && trim($submittedFormField->Value)) {
650
                        $email->setSubject($submittedFormField->Value);
651
                    }
652
                }
653
654
                $this->extend('updateEmail', $email, $recipient, $emailData);
655
656 9
                if ($recipient->SendPlain) {
657
                    $body = strip_tags($recipient->getEmailBodyContent()) . "\n";
658
                    if (isset($emailData['Fields']) && !$recipient->HideFormData) {
659
                        foreach ($emailData['Fields'] as $Field) {
660
                            $body .= $Field->Title .': '. $Field->Value ." \n";
661
                        }
662
                    }
663
664
                    $email->setBody($body);
665
                    $email->sendPlain();
666
                } else {
667
                    $email->send();
668 3
                }
669 3
            }
670 2
        }
671 2
672 2
        $submittedForm->extend('updateAfterProcess');
673
674
        Session::clear("FormInfo.{$form->FormName()}.errors");
675 2
        Session::clear("FormInfo.{$form->FormName()}.data");
676 2
677 2
        $referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
678
679 2
        // set a session variable from the security ID to stop people accessing
680 2
        // the finished method directly.
681
        if (!$this->DisableAuthenicatedFinishAction) {
682 2
            if (isset($data['SecurityID'])) {
683 2
                Session::set('FormProcessed', $data['SecurityID']);
684 2
            } else {
685
                // if the form has had tokens disabled we still need to set FormProcessed
686
                // to allow us to get through the finshed method
687 2
                if (!$this->Form()->getSecurityToken()->isEnabled()) {
688 2
                    $randNum = rand(1, 1000);
689 2
                    $randHash = md5($randNum);
690 2
                    Session::set('FormProcessed', $randHash);
691
                    Session::set('FormProcessedNum', $randNum);
692
                }
693 2
            }
694
        }
695
696 2
        if (!$this->DisableSaveSubmissions) {
697 2
            Session::set('userformssubmission'. $this->ID, $submittedForm->ID);
698 2
        }
699
700
        return $this->redirect($this->Link('finished') . $referrer . $this->config()->finished_anchor);
701 2
    }
702 2
703
    /**
704
     * Allows the use of field values in email body.
705
     *
706
     * @param ArrayList fields
707
     * @return ArrayData
708
     */
709
    private function getMergeFieldsMap($fields = array())
710
    {
711
        $data = new ArrayData(array());
712
713
        foreach ($fields as $field) {
714
            $data->setField($field->Name, DBField::create_field('Text', $field->Value));
715
        }
716
717
        return $data;
718
    }
719
720
    /**
721
     * This action handles rendering the "finished" message, which is
722
     * customizable by editing the ReceivedFormSubmission template.
723
     *
724
     * @return ViewableData
725
     */
726
    public function finished()
0 ignored issues
show
Coding Style introduced by
finished uses the super-global variable $_GET which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
727
    {
728 2
        $submission = Session::get('userformssubmission'. $this->ID);
729
730 2
        if ($submission) {
731
            $submission = SubmittedForm::get()->byId($submission);
732 2
        }
733 2
734 2
        $referrer = isset($_GET['referrer']) ? urldecode($_GET['referrer']) : null;
735
736 2
        if (!$this->DisableAuthenicatedFinishAction) {
737 2
            $formProcessed = Session::get('FormProcessed');
738
739
            if (!isset($formProcessed)) {
740 2
                return $this->redirect($this->Link() . $referrer);
741
            } else {
742 2
                $securityID = Session::get('SecurityID');
743
                // make sure the session matches the SecurityID and is not left over from another form
744 2
                if ($formProcessed != $securityID) {
745
                    // they may have disabled tokens on the form
746
                    $securityID = md5(Session::get('FormProcessedNum'));
747 2
                    if ($formProcessed != $securityID) {
748 2
                        return $this->redirect($this->Link() . $referrer);
749 1
                    }
750 1
                }
751
            }
752 1
753
            Session::clear('FormProcessed');
754
        }
755
756
        $data = array(
757
                'Submission' => $submission,
758
                'Link' => $referrer
759
        );
760
761
        $this->extend('updateReceivedFormSubmissionData', $data);
762
763
        return $this->customise(array(
764 1
            'Content' => $this->customise($data)->renderWith('ReceivedFormSubmission'),
765
            'Form' => '',
766 1
        ));
767
    }
768
769
    /**
770 1
     * Outputs the required JS from the $watch input
771 1
     *
772 1
     * @param array $watch
773 1
     *
774 1
     * @return string
775 1
     */
776
    protected function buildWatchJS($watch)
777 1
    {
778
        $result = '';
779
        foreach ($watch as $key => $rule) {
780
            $events = implode(' ', $rule['events']);
781
            $selectors = implode(', ', $rule['selectors']);
782 1
            $conjunction = $rule['conjunction'];
783 1
            $operations = implode(" {$conjunction} ", $rule['operations']);
784
            $target = $rule['targetFieldID'];
785 1
            $initialState = $rule['initialState'];
786
            $view = $rule['view'];
787
            $opposite = $rule['opposite'];
788 1
789
            $result .= <<<EOS
790 1
\n
791 1
    //Initial state
792
    $('{$target}').{$initialState}();
793 1
794
    $('.userform').on('{$events}',
795
    "{$selectors}",
796 1
    function (){
797
        if({$operations}) {
798
            $('{$target}').{$view}();
799 1
        } else {
800 1
            $('{$target}').{$opposite}();
801
        }
802 1
    });
803
EOS;
804
805 1
        }
806
807 1
        return $result;
808
    }
809 1
810
}
811