Completed
Push — master ( 644ae6...bba86b )
by Daniel
10:38
created

FormTest::testButtonClicked()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 44
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 34
nc 1
nop 0
dl 0
loc 44
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Forms\Tests;
4
5
use SilverStripe\Forms\Tests\FormTest\TestController;
6
use SilverStripe\Forms\Tests\FormTest\ControllerWithSecurityToken;
7
use SilverStripe\Forms\Tests\FormTest\ControllerWithStrictPostCheck;
8
use SilverStripe\Forms\Tests\FormTest\Player;
9
use SilverStripe\Forms\Tests\FormTest\Team;
10
use SilverStripe\ORM\ValidationResult;
11
use SilverStripe\Security\NullSecurityToken;
12
use SilverStripe\Security\SecurityToken;
13
use SilverStripe\Security\RandomGenerator;
14
use SilverStripe\Dev\CSSContentParser;
15
use SilverStripe\Dev\FunctionalTest;
16
use SilverStripe\Control\Controller;
17
use SilverStripe\Control\HTTPRequest;
18
use SilverStripe\Forms\TextField;
19
use SilverStripe\Forms\FieldList;
20
use SilverStripe\Forms\Form;
21
use SilverStripe\Forms\HeaderField;
22
use SilverStripe\Forms\TextareaField;
23
use SilverStripe\Forms\DateField;
24
use SilverStripe\Forms\NumericField;
25
use SilverStripe\Forms\LookupField;
26
use SilverStripe\Forms\FileField;
27
use SilverStripe\Forms\FormAction;
28
use SilverStripe\View\SSViewer;
29
30
/**
31
 * @skipUpgrade
32
 */
33
class FormTest extends FunctionalTest
34
{
35
36
    protected static $fixture_file = 'FormTest.yml';
37
38
    protected $extraDataObjects = array(
39
        Player::class,
40
        Team::class,
41
    );
42
43
    protected $extraControllers = [
44
        TestController::class,
45
        ControllerWithSecurityToken::class,
46
        ControllerWithStrictPostCheck::class,
47
    ];
48
49
    public function setUp()
50
    {
51
        parent::setUp();
52
53
        // Suppress themes
54
        SSViewer::set_themes(
55
            [
56
            SSViewer::DEFAULT_THEME
57
            ]
58
        );
59
    }
60
61
    public function testLoadDataFromRequest()
62
    {
63
        $form = new Form(
64
            new Controller(),
65
            'Form',
66
            new FieldList(
67
                new TextField('key1'),
68
                new TextField('namespace[key2]'),
69
                new TextField('namespace[key3][key4]'),
70
                new TextField('othernamespace[key5][key6][key7]')
71
            ),
72
            new FieldList()
73
        );
74
75
        // url would be ?key1=val1&namespace[key2]=val2&namespace[key3][key4]=val4&othernamespace[key5][key6][key7]=val7
0 ignored issues
show
Unused Code Comprehensibility introduced by
44% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
76
        $requestData = array(
77
            'key1' => 'val1',
78
            'namespace' => array(
79
                'key2' => 'val2',
80
                'key3' => array(
81
                    'key4' => 'val4',
82
                )
83
            ),
84
            'othernamespace' => array(
85
                'key5' => array(
86
                    'key6' =>array(
87
                        'key7' => 'val7'
88
                    )
89
                )
90
            )
91
        );
92
93
        $form->loadDataFrom($requestData);
94
95
        $fields = $form->Fields();
96
        $this->assertEquals('val1', $fields->fieldByName('key1')->Value());
97
        $this->assertEquals('val2', $fields->fieldByName('namespace[key2]')->Value());
98
        $this->assertEquals('val4', $fields->fieldByName('namespace[key3][key4]')->Value());
99
        $this->assertEquals('val7', $fields->fieldByName('othernamespace[key5][key6][key7]')->Value());
100
    }
101
102
    public function testSubmitReadonlyFields()
103
    {
104
        $this->get('FormTest_Controller');
105
106
        // Submitting a value for a readonly field should be ignored
107
        $response = $this->post(
108
            'FormTest_Controller/Form',
109
            array(
110
                'Email' => 'invalid',
111
                'Number' => '888',
112
                'ReadonlyField' => '<script>alert("hacxzored")</script>'
113
                // leaving out "Required" field
114
            )
115
        );
116
117
        // Number field updates its value
118
        $this->assertContains('<input type="text" name="Number" value="888"', $response->getBody());
119
120
121
        // Readonly field remains
122
        $this->assertContains(
123
            '<input type="text" name="ReadonlyField" value="This value is readonly"',
124
            $response->getBody()
125
        );
126
127
        $this->assertNotContains('hacxzored', $response->getBody());
128
    }
129
130
    public function testLoadDataFromUnchangedHandling()
131
    {
132
        $form = new Form(
133
            new Controller(),
134
            'Form',
135
            new FieldList(
136
                new TextField('key1'),
137
                new TextField('key2')
138
            ),
139
            new FieldList()
140
        );
141
        $form->loadDataFrom(
142
            array(
143
            'key1' => 'save',
144
            'key2' => 'dontsave',
145
            'key2_unchanged' => '1'
146
            )
147
        );
148
        $this->assertEquals(
149
            $form->getData(),
150
            array(
151
                'key1' => 'save',
152
                'key2' => null,
153
            ),
154
            'loadDataFrom() doesnt save a field if a matching "<fieldname>_unchanged" flag is set'
155
        );
156
    }
157
158
    public function testLoadDataFromObject()
159
    {
160
        $form = new Form(
161
            new Controller(),
162
            'Form',
163
            new FieldList(
164
                new HeaderField('MyPlayerHeader', 'My Player'),
165
                new TextField('Name'), // appears in both Player and Team
166
                new TextareaField('Biography'),
167
                new DateField('Birthday'),
168
                new NumericField('BirthdayYear') // dynamic property
169
            ),
170
            new FieldList()
171
        );
172
173
        $captainWithDetails = $this->objFromFixture(Player::class, 'captainWithDetails');
174
        $form->loadDataFrom($captainWithDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture(\S..., 'captainWithDetails') on line 173 can be null; however, SilverStripe\Forms\Form::loadDataFrom() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
175
        $this->assertEquals(
176
            $form->getData(),
177
            array(
178
                'Name' => 'Captain Details',
179
                'Biography' => 'Bio 1',
180
                'Birthday' => '1982-01-01',
181
                'BirthdayYear' => '1982',
182
            ),
183
            'LoadDataFrom() loads simple fields and dynamic getters'
184
        );
185
186
        $captainNoDetails = $this->objFromFixture(Player::class, 'captainNoDetails');
187
        $form->loadDataFrom($captainNoDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainNoDetails defined by $this->objFromFixture(\S...ss, 'captainNoDetails') on line 186 can be null; however, SilverStripe\Forms\Form::loadDataFrom() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
188
        $this->assertEquals(
189
            $form->getData(),
190
            array(
191
                'Name' => 'Captain No Details',
192
                'Biography' => null,
193
                'Birthday' => null,
194
                'BirthdayYear' => 0,
195
            ),
196
            'LoadNonBlankDataFrom() loads only fields with values, and doesnt overwrite existing values'
197
        );
198
    }
199
200
    public function testLoadDataFromClearMissingFields()
201
    {
202
        $form = new Form(
203
            new Controller(),
204
            'Form',
205
            new FieldList(
206
                new HeaderField('MyPlayerHeader', 'My Player'),
207
                new TextField('Name'), // appears in both Player and Team
208
                new TextareaField('Biography'),
209
                new DateField('Birthday'),
210
                new NumericField('BirthdayYear'), // dynamic property
211
                $unrelatedField = new TextField('UnrelatedFormField')
212
                //new CheckboxSetField('Teams') // relation editing
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
213
            ),
214
            new FieldList()
215
        );
216
        $unrelatedField->setValue("random value");
217
218
        $captainWithDetails = $this->objFromFixture(Player::class, 'captainWithDetails');
219
        $form->loadDataFrom($captainWithDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture(\S..., 'captainWithDetails') on line 218 can be null; however, SilverStripe\Forms\Form::loadDataFrom() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
220
        $this->assertEquals(
221
            $form->getData(),
222
            array(
223
                'Name' => 'Captain Details',
224
                'Biography' => 'Bio 1',
225
                'Birthday' => '1982-01-01',
226
                'BirthdayYear' => '1982',
227
                'UnrelatedFormField' => 'random value',
228
            ),
229
            'LoadDataFrom() doesnt overwrite fields not found in the object'
230
        );
231
232
        $captainWithDetails = $this->objFromFixture(Player::class, 'captainNoDetails');
233
        $team2 = $this->objFromFixture(Team::class, 'team2');
234
        $form->loadDataFrom($captainWithDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture(\S...ss, 'captainNoDetails') on line 232 can be null; however, SilverStripe\Forms\Form::loadDataFrom() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
235
        $form->loadDataFrom($team2, Form::MERGE_CLEAR_MISSING);
0 ignored issues
show
Bug introduced by
It seems like $team2 defined by $this->objFromFixture(\S...t\Team::class, 'team2') on line 233 can be null; however, SilverStripe\Forms\Form::loadDataFrom() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
236
        $this->assertEquals(
237
            $form->getData(),
238
            array(
239
                'Name' => 'Team 2',
240
                'Biography' => '',
241
                'Birthday' => '',
242
                'BirthdayYear' => 0,
243
                'UnrelatedFormField' => null,
244
            ),
245
            'LoadDataFrom() overwrites fields not found in the object with $clearMissingFields=true'
246
        );
247
    }
248
249
    public function testLookupFieldDisabledSaving()
250
    {
251
        $object = new Team();
252
        $form = new Form(
253
            new Controller(),
254
            'Form',
255
            new FieldList(
256
                new LookupField('Players', 'Players')
257
            ),
258
            new FieldList()
259
        );
260
        $form->loadDataFrom(
261
            array(
262
            'Players' => array(
263
                14,
264
                18,
265
                22
266
            ),
267
            )
268
        );
269
        $form->saveInto($object);
270
        $playersIds = $object->Players()->getIDList();
271
272
        $this->assertTrue($form->validationResult()->isValid());
273
        $this->assertEquals(
274
            $playersIds,
275
            array(),
276
            'saveInto() should not save into the DataObject for the LookupField'
277
        );
278
    }
279
280
    public function testLoadDataFromIgnoreFalseish()
281
    {
282
        $form = new Form(
283
            new Controller(),
284
            'Form',
285
            new FieldList(
286
                new TextField('Biography', 'Biography', 'Custom Default')
287
            ),
288
            new FieldList()
289
        );
290
291
        $captainNoDetails = $this->objFromFixture(Player::class, 'captainNoDetails');
292
        $captainWithDetails = $this->objFromFixture(Player::class, 'captainWithDetails');
293
294
        $form->loadDataFrom($captainNoDetails, Form::MERGE_IGNORE_FALSEISH);
0 ignored issues
show
Bug introduced by
It seems like $captainNoDetails defined by $this->objFromFixture(\S...ss, 'captainNoDetails') on line 291 can be null; however, SilverStripe\Forms\Form::loadDataFrom() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
295
        $this->assertEquals(
296
            $form->getData(),
297
            array('Biography' => 'Custom Default'),
298
            'LoadDataFrom() doesn\'t overwrite fields when MERGE_IGNORE_FALSEISH set and values are false-ish'
299
        );
300
301
        $form->loadDataFrom($captainWithDetails, Form::MERGE_IGNORE_FALSEISH);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture(\S..., 'captainWithDetails') on line 292 can be null; however, SilverStripe\Forms\Form::loadDataFrom() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
302
        $this->assertEquals(
303
            $form->getData(),
304
            array('Biography' => 'Bio 1'),
305
            'LoadDataFrom() does overwrite fields when MERGE_IGNORE_FALSEISH set and values arent false-ish'
306
        );
307
    }
308
309
    public function testFormMethodOverride()
310
    {
311
        $form = $this->getStubForm();
312
        $form->setFormMethod('GET');
313
        $this->assertNull($form->Fields()->dataFieldByName('_method'));
314
315
        $form = $this->getStubForm();
316
        $form->setFormMethod('PUT');
317
        $this->assertEquals(
318
            $form->Fields()->dataFieldByName('_method')->Value(),
319
            'PUT',
320
            'PUT override in forms has PUT in hiddenfield'
321
        );
322
        $this->assertEquals(
323
            $form->FormMethod(),
324
            'POST',
325
            'PUT override in forms has POST in <form> tag'
326
        );
327
328
        $form = $this->getStubForm();
329
        $form->setFormMethod('DELETE');
330
        $this->assertEquals(
331
            $form->Fields()->dataFieldByName('_method')->Value(),
332
            'DELETE',
333
            'PUT override in forms has PUT in hiddenfield'
334
        );
335
        $this->assertEquals(
336
            $form->FormMethod(),
337
            'POST',
338
            'PUT override in forms has POST in <form> tag'
339
        );
340
    }
341
342
    public function testValidationExemptActions()
343
    {
344
        $this->get('FormTest_Controller');
345
346
        $this->submitForm(
347
            'Form_Form',
348
            'action_doSubmit',
349
            array(
350
                'Email' => '[email protected]'
351
            )
352
        );
353
354
        // Firstly, assert that required fields still work when not using an exempt action
355
        $this->assertPartialMatchBySelector(
356
            '#Form_Form_SomeRequiredField_Holder .required',
357
            array('"Some Required Field" is required'),
358
            'Required fields show a notification on field when left blank'
359
        );
360
361
        // Re-submit the form using validation-exempt button
362
        $this->submitForm(
363
            'Form_Form',
364
            'action_doSubmitValidationExempt',
365
            array(
366
                'Email' => '[email protected]'
367
            )
368
        );
369
370
        // The required message should be empty if validation was skipped
371
        $items = $this->cssParser()->getBySelector('#Form_Form_SomeRequiredField_Holder .required');
372
        $this->assertEmpty($items);
373
374
        // And the session message should show up is submitted successfully
375
        $this->assertPartialMatchBySelector(
376
            '#Form_Form_error',
377
            array(
378
                'Validation skipped'
379
            ),
380
            'Form->sessionMessage() shows up after reloading the form'
381
        );
382
383
        // Test this same behaviour, but with a form-action exempted via instance
384
        $this->submitForm(
385
            'Form_Form',
386
            'action_doSubmitActionExempt',
387
            array(
388
                'Email' => '[email protected]'
389
            )
390
        );
391
392
        // The required message should be empty if validation was skipped
393
        $items = $this->cssParser()->getBySelector('#Form_Form_SomeRequiredField_Holder .required');
394
        $this->assertEmpty($items);
395
396
        // And the session message should show up is submitted successfully
397
        $this->assertPartialMatchBySelector(
398
            '#Form_Form_error',
399
            array(
400
                'Validation bypassed!'
401
            ),
402
            'Form->sessionMessage() shows up after reloading the form'
403
        );
404
    }
405
406
    public function testSessionValidationMessage()
407
    {
408
        $this->get('FormTest_Controller');
409
410
        $response = $this->post(
411
            'FormTest_Controller/Form',
412
            array(
413
                'Email' => 'invalid',
414
                'Number' => '<a href="http://mysite.com">link</a>' // XSS attempt
415
                // leaving out "Required" field
416
            )
417
        );
418
419
        $this->assertPartialMatchBySelector(
420
            '#Form_Form_Email_Holder span.message',
421
            array(
422
                'Please enter an email address'
423
            ),
424
            'Formfield validation shows note on field if invalid'
425
        );
426
        $this->assertPartialMatchBySelector(
427
            '#Form_Form_SomeRequiredField_Holder span.required',
428
            array(
429
                '"Some Required Field" is required'
430
            ),
431
            'Required fields show a notification on field when left blank'
432
        );
433
434
        $this->assertContains(
435
            '&#039;&lt;a href=&quot;http://mysite.com&quot;&gt;link&lt;/a&gt;&#039; is not a number, only numbers can be accepted for this field',
436
            $response->getBody(),
437
            "Validation messages are safely XML encoded"
438
        );
439
        $this->assertNotContains(
440
            '<a href="http://mysite.com">link</a>',
441
            $response->getBody(),
442
            "Unsafe content is not emitted directly inside the response body"
443
        );
444
    }
445
446
    public function testSessionSuccessMessage()
447
    {
448
        $this->get('FormTest_Controller');
449
450
        $result = $this->post(
451
            'FormTest_Controller/Form',
452
            array(
453
                'Email' => '[email protected]',
454
                'SomeRequiredField' => 'test',
455
            )
456
        );
457
        $this->assertPartialMatchBySelector(
458
            '#Form_Form_error',
459
            array(
460
                'Test save was successful'
461
            ),
462
            'Form->sessionMessage() shows up after reloading the form'
463
        );
464
    }
465
466
    public function testValidationException()
467
    {
468
        $this->get('FormTest_Controller');
469
470
        $this->post(
471
            'FormTest_Controller/Form',
472
            array(
473
                'Email' => '[email protected]',
474
                'SomeRequiredField' => 'test',
475
                'action_doTriggerException' => 1,
476
            )
477
        );
478
        $this->assertPartialMatchBySelector(
479
            '#Form_Form_Email_Holder span.message',
480
            array(
481
                'Error on Email field'
482
            ),
483
            'Formfield validation shows note on field if invalid'
484
        );
485
        $this->assertPartialMatchBySelector(
486
            '#Form_Form_error',
487
            array(
488
                'Error at top of form'
489
            ),
490
            'Required fields show a notification on field when left blank'
491
        );
492
    }
493
494
    public function testGloballyDisabledSecurityTokenInheritsToNewForm()
495
    {
496
        SecurityToken::enable();
497
498
        $form1 = $this->getStubForm();
499
        $this->assertInstanceOf(SecurityToken::class, $form1->getSecurityToken());
500
501
        SecurityToken::disable();
502
503
        $form2 = $this->getStubForm();
504
        $this->assertInstanceOf(NullSecurityToken::class, $form2->getSecurityToken());
505
506
        SecurityToken::enable();
507
    }
508
509
    public function testDisableSecurityTokenDoesntAddTokenFormField()
510
    {
511
        SecurityToken::enable();
512
513
        $formWithToken = $this->getStubForm();
514
        $this->assertInstanceOf(
515
            'SilverStripe\\Forms\\HiddenField',
516
            $formWithToken->Fields()->fieldByName(SecurityToken::get_default_name()),
517
            'Token field added by default'
518
        );
519
520
        $formWithoutToken = $this->getStubForm();
521
        $formWithoutToken->disableSecurityToken();
522
        $this->assertNull(
523
            $formWithoutToken->Fields()->fieldByName(SecurityToken::get_default_name()),
524
            'Token field not added if disableSecurityToken() is set'
525
        );
526
    }
527
528
    public function testDisableSecurityTokenAcceptsSubmissionWithoutToken()
529
    {
530
        SecurityToken::enable();
531
        $expectedToken = SecurityToken::inst()->getValue();
532
533
        $this->get('FormTest_ControllerWithSecurityToken');
534
        // can't use submitForm() as it'll automatically insert SecurityID into the POST data
535
        $response = $this->post(
536
            'FormTest_ControllerWithSecurityToken/Form',
537
            array(
538
                'Email' => '[email protected]',
539
                'action_doSubmit' => 1
540
                // leaving out security token
541
            )
542
        );
543
        $this->assertEquals(400, $response->getStatusCode(), 'Submission fails without security token');
544
545
        // Generate a new token which doesn't match the current one
546
        $generator = new RandomGenerator();
547
        $invalidToken = $generator->randomToken('sha1');
548
        $this->assertNotEquals($invalidToken, $expectedToken);
549
550
        // Test token with request
551
        $this->get('FormTest_ControllerWithSecurityToken');
552
        $response = $this->post(
553
            'FormTest_ControllerWithSecurityToken/Form',
554
            array(
555
                'Email' => '[email protected]',
556
                'action_doSubmit' => 1,
557
                'SecurityID' => $invalidToken
558
            )
559
        );
560
        $this->assertEquals(200, $response->getStatusCode(), 'Submission reloads form if security token invalid');
561
        $this->assertTrue(
562
            stripos($response->getBody(), 'name="SecurityID" value="'.$expectedToken.'"') !== false,
563
            'Submission reloads with correct security token after failure'
564
        );
565
        $this->assertTrue(
566
            stripos($response->getBody(), 'name="SecurityID" value="'.$invalidToken.'"') === false,
567
            'Submission reloads without incorrect security token after failure'
568
        );
569
570
        $matched = $this->cssParser()->getBySelector('#Form_Form_Email');
571
        $attrs = $matched[0]->attributes();
572
        $this->assertEquals('[email protected]', (string)$attrs['value'], 'Submitted data is preserved');
573
574
        $this->get('FormTest_ControllerWithSecurityToken');
575
        $tokenEls = $this->cssParser()->getBySelector('#Form_Form_SecurityID');
576
        $this->assertEquals(
577
            1,
578
            count($tokenEls),
579
            'Token form field added for controller without disableSecurityToken()'
580
        );
581
        $token = (string)$tokenEls[0];
582
        $response = $this->submitForm(
583
            'Form_Form',
584
            null,
585
            array(
586
                'Email' => '[email protected]',
587
                'SecurityID' => $token
588
            )
589
        );
590
        $this->assertEquals(200, $response->getStatusCode(), 'Submission suceeds with security token');
591
    }
592
593
    public function testStrictFormMethodChecking()
594
    {
595
        $this->get('FormTest_ControllerWithStrictPostCheck');
596
        $response = $this->get(
597
            'FormTest_ControllerWithStrictPostCheck/Form/[email protected]&action_doSubmit=1'
598
        );
599
        $this->assertEquals(405, $response->getStatusCode(), 'Submission fails with wrong method');
600
601
        $this->get('FormTest_ControllerWithStrictPostCheck');
602
        $response = $this->post(
603
            'FormTest_ControllerWithStrictPostCheck/Form',
604
            array(
605
                'Email' => '[email protected]',
606
                'action_doSubmit' => 1
607
            )
608
        );
609
        $this->assertEquals(200, $response->getStatusCode(), 'Submission succeeds with correct method');
610
    }
611
612
    public function testEnableSecurityToken()
613
    {
614
        SecurityToken::disable();
615
        $form = $this->getStubForm();
616
        $this->assertFalse($form->getSecurityToken()->isEnabled());
617
        $form->enableSecurityToken();
618
        $this->assertTrue($form->getSecurityToken()->isEnabled());
619
620
        SecurityToken::disable(); // restore original
621
    }
622
623
    public function testDisableSecurityToken()
624
    {
625
        SecurityToken::enable();
626
        $form = $this->getStubForm();
627
        $this->assertTrue($form->getSecurityToken()->isEnabled());
628
        $form->disableSecurityToken();
629
        $this->assertFalse($form->getSecurityToken()->isEnabled());
630
631
        SecurityToken::disable(); // restore original
632
    }
633
634
    public function testEncType()
635
    {
636
        $form = $this->getStubForm();
637
        $this->assertEquals('application/x-www-form-urlencoded', $form->getEncType());
638
639
        $form->setEncType(Form::ENC_TYPE_MULTIPART);
640
        $this->assertEquals('multipart/form-data', $form->getEncType());
641
642
        $form = $this->getStubForm();
643
        $form->Fields()->push(new FileField(null));
644
        $this->assertEquals('multipart/form-data', $form->getEncType());
645
646
        $form->setEncType(Form::ENC_TYPE_URLENCODED);
647
        $this->assertEquals('application/x-www-form-urlencoded', $form->getEncType());
648
    }
649
650
    public function testAddExtraClass()
651
    {
652
        $form = $this->getStubForm();
653
        $form->addExtraClass('class1');
654
        $form->addExtraClass('class2');
655
        $this->assertStringEndsWith('class1 class2', $form->extraClass());
656
    }
657
658
    public function testRemoveExtraClass()
659
    {
660
        $form = $this->getStubForm();
661
        $form->addExtraClass('class1');
662
        $form->addExtraClass('class2');
663
        $this->assertStringEndsWith('class1 class2', $form->extraClass());
664
        $form->removeExtraClass('class1');
665
        $this->assertStringEndsWith('class2', $form->extraClass());
666
    }
667
668
    public function testAddManyExtraClasses()
669
    {
670
        $form = $this->getStubForm();
671
        //test we can split by a range of spaces and tabs
672
        $form->addExtraClass('class1 class2     class3	class4		class5');
673
        $this->assertStringEndsWith(
674
            'class1 class2 class3 class4 class5',
675
            $form->extraClass()
676
        );
677
        //test that duplicate classes don't get added
678
        $form->addExtraClass('class1 class2');
679
        $this->assertStringEndsWith(
680
            'class1 class2 class3 class4 class5',
681
            $form->extraClass()
682
        );
683
    }
684
685
    public function testRemoveManyExtraClasses()
686
    {
687
        $form = $this->getStubForm();
688
        $form->addExtraClass('class1 class2     class3	class4		class5');
689
        //test we can remove a single class we just added
690
        $form->removeExtraClass('class3');
691
        $this->assertStringEndsWith(
692
            'class1 class2 class4 class5',
693
            $form->extraClass()
694
        );
695
        //check we can remove many classes at once
696
        $form->removeExtraClass('class1 class5');
697
        $this->assertStringEndsWith(
698
            'class2 class4',
699
            $form->extraClass()
700
        );
701
        //check that removing a dud class is fine
702
        $form->removeExtraClass('dudClass');
703
        $this->assertStringEndsWith(
704
            'class2 class4',
705
            $form->extraClass()
706
        );
707
    }
708
709
    public function testDefaultClasses()
710
    {
711
        Form::config()->update(
712
            'default_classes',
713
            array(
714
            'class1',
715
            )
716
        );
717
718
        $form = $this->getStubForm();
719
720
        $this->assertContains('class1', $form->extraClass(), 'Class list does not contain expected class');
721
722
        Form::config()->update(
723
            'default_classes',
724
            array(
725
            'class1',
726
            'class2',
727
            )
728
        );
729
730
        $form = $this->getStubForm();
731
732
        $this->assertContains('class1 class2', $form->extraClass(), 'Class list does not contain expected class');
733
734
        Form::config()->update(
735
            'default_classes',
736
            array(
737
            'class3',
738
            )
739
        );
740
741
        $form = $this->getStubForm();
742
743
        $this->assertContains('class3', $form->extraClass(), 'Class list does not contain expected class');
744
745
        $form->removeExtraClass('class3');
746
747
        $this->assertNotContains('class3', $form->extraClass(), 'Class list contains unexpected class');
748
    }
749
750
    public function testAttributes()
751
    {
752
        $form = $this->getStubForm();
753
        $form->setAttribute('foo', 'bar');
754
        $this->assertEquals('bar', $form->getAttribute('foo'));
755
        $attrs = $form->getAttributes();
756
        $this->assertArrayHasKey('foo', $attrs);
757
        $this->assertEquals('bar', $attrs['foo']);
758
    }
759
760
    /**
761
     * @skipUpgrade
762
     */
763
    public function testButtonClicked()
764
    {
765
        $form = $this->getStubForm();
766
        $action = $form->getRequestHandler()->buttonClicked();
767
        $this->assertNull($action);
768
769
        $controller = new FormTest\TestController();
770
        $form = $controller->Form();
771
        $request = new HTTPRequest(
772
            'POST',
773
            'FormTest_Controller/Form',
774
            array(),
775
            array(
776
            'Email' => '[email protected]',
777
            'SomeRequiredField' => 1,
778
            'action_doSubmit' => 1
779
            )
780
        );
781
782
        $form->getRequestHandler()->httpSubmission($request);
783
        $button = $form->getRequestHandler()->buttonClicked();
784
        $this->assertInstanceOf(FormAction::class, $button);
785
        $this->assertEquals('doSubmit', $button->actionName());
786
        $form = new Form(
787
            $controller,
788
            'Form',
789
            new FieldList(new FormAction('doSubmit', 'Inline action')),
790
            new FieldList()
791
        );
792
        $form->disableSecurityToken();
793
        $request = new HTTPRequest(
794
            'POST',
795
            'FormTest_Controller/Form',
796
            array(),
797
            array(
798
            'action_doSubmit' => 1
799
            )
800
        );
801
802
        $form->getRequestHandler()->httpSubmission($request);
803
        $button = $form->getRequestHandler()->buttonClicked();
804
        $this->assertInstanceOf(FormAction::class, $button);
805
        $this->assertEquals('doSubmit', $button->actionName());
806
    }
807
808
    public function testCheckAccessAction()
809
    {
810
        $controller = new FormTest\TestController();
811
        $form = new Form(
812
            $controller,
813
            'Form',
814
            new FieldList(),
815
            new FieldList(new FormAction('actionName', 'Action'))
816
        );
817
        $this->assertTrue($form->getRequestHandler()->checkAccessAction('actionName'));
818
819
        $form = new Form(
820
            $controller,
821
            'Form',
822
            new FieldList(new FormAction('inlineAction', 'Inline action')),
823
            new FieldList()
824
        );
825
        $this->assertTrue($form->getRequestHandler()->checkAccessAction('inlineAction'));
826
    }
827
828
    public function testAttributesHTML()
829
    {
830
        $form = $this->getStubForm();
831
832
        $form->setAttribute('foo', 'bar');
833
        $this->assertContains('foo="bar"', $form->getAttributesHTML());
834
835
        $form->setAttribute('foo', null);
836
        $this->assertNotContains('foo="bar"', $form->getAttributesHTML());
837
838
        $form->setAttribute('foo', true);
839
        $this->assertContains('foo="foo"', $form->getAttributesHTML());
840
841
        $form->setAttribute('one', 1);
842
        $form->setAttribute('two', 2);
843
        $form->setAttribute('three', 3);
844
        $this->assertNotContains('one="1"', $form->getAttributesHTML('one', 'two'));
845
        $this->assertNotContains('two="2"', $form->getAttributesHTML('one', 'two'));
846
        $this->assertContains('three="3"', $form->getAttributesHTML('one', 'two'));
847
    }
848
849
    function testMessageEscapeHtml()
850
    {
851
        $form = $this->getStubForm();
852
        $form->setMessage('<em>Escaped HTML</em>', 'good', ValidationResult::CAST_TEXT);
853
        $parser = new CSSContentParser($form->forTemplate());
854
        $messageEls = $parser->getBySelector('.message');
855
        $this->assertContains(
856
            '&lt;em&gt;Escaped HTML&lt;/em&gt;',
857
            $messageEls[0]->asXML()
858
        );
859
860
        $form = $this->getStubForm();
861
        $form->setMessage('<em>Unescaped HTML</em>', 'good', ValidationResult::CAST_HTML);
862
        $parser = new CSSContentParser($form->forTemplate());
863
        $messageEls = $parser->getBySelector('.message');
864
        $this->assertContains(
865
            '<em>Unescaped HTML</em>',
866
            $messageEls[0]->asXML()
867
        );
868
    }
869
870
    public function testFieldMessageEscapeHtml()
871
    {
872
        $form = $this->getStubForm();
873
        $form->Fields()->dataFieldByName('key1')->setMessage('<em>Escaped HTML</em>', 'good');
874
        $parser = new CSSContentParser($result = $form->forTemplate());
875
        $messageEls = $parser->getBySelector('#Form_Form_key1_Holder .message');
876
        $this->assertContains(
877
            '&lt;em&gt;Escaped HTML&lt;/em&gt;',
878
            $messageEls[0]->asXML()
879
        );
880
881
        // Test with HTML
882
        $form = $this->getStubForm();
883
        $form
884
            ->Fields()
885
            ->dataFieldByName('key1')
886
            ->setMessage('<em>Unescaped HTML</em>', 'good', ValidationResult::CAST_HTML);
887
        $parser = new CSSContentParser($form->forTemplate());
888
        $messageEls = $parser->getBySelector('#Form_Form_key1_Holder .message');
889
        $this->assertContains(
890
            '<em>Unescaped HTML</em>',
891
            $messageEls[0]->asXML()
892
        );
893
    }
894
895
    public function testGetExtraFields()
896
    {
897
        $form = new FormTest\ExtraFieldsForm(
898
            new FormTest\TestController(),
899
            'Form',
900
            new FieldList(new TextField('key1')),
901
            new FieldList()
902
        );
903
904
        $data = array(
905
            'key1' => 'test',
906
            'ExtraFieldCheckbox' => false,
907
        );
908
909
        $form->loadDataFrom($data);
910
911
        $formData = $form->getData();
912
        $this->assertEmpty($formData['ExtraFieldCheckbox']);
913
    }
914
915
    protected function getStubForm()
916
    {
917
        return new Form(
918
            new FormTest\TestController(),
919
            'Form',
920
            new FieldList(new TextField('key1')),
921
            new FieldList()
922
        );
923
    }
924
}
925