Passed
Push — master ( 16e851...25995c )
by Daniel
09:34
created

FormTest::testRestoreFromState()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 50
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 29
nc 1
nop 0
dl 0
loc 50
rs 9.456
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Forms\Tests;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Control\HTTPRequest;
7
use SilverStripe\Control\Session;
8
use SilverStripe\Dev\CSSContentParser;
9
use SilverStripe\Dev\FunctionalTest;
10
use SilverStripe\Forms\DateField;
11
use SilverStripe\Forms\DatetimeField;
12
use SilverStripe\Forms\FieldList;
13
use SilverStripe\Forms\FileField;
14
use SilverStripe\Forms\Form;
15
use SilverStripe\Forms\FormAction;
16
use SilverStripe\Forms\HeaderField;
17
use SilverStripe\Forms\LookupField;
18
use SilverStripe\Forms\NumericField;
19
use SilverStripe\Forms\PasswordField;
20
use SilverStripe\Forms\Tests\FormTest\ControllerWithSecurityToken;
21
use SilverStripe\Forms\Tests\FormTest\ControllerWithSpecialSubmittedValueFields;
22
use SilverStripe\Forms\Tests\FormTest\ControllerWithStrictPostCheck;
23
use SilverStripe\Forms\Tests\FormTest\Player;
24
use SilverStripe\Forms\Tests\FormTest\Team;
25
use SilverStripe\Forms\Tests\FormTest\TestController;
26
use SilverStripe\Forms\Tests\ValidatorTest\TestValidator;
27
use SilverStripe\Forms\TextareaField;
28
use SilverStripe\Forms\TextField;
29
use SilverStripe\Forms\TimeField;
30
use SilverStripe\ORM\ValidationResult;
31
use SilverStripe\Security\NullSecurityToken;
32
use SilverStripe\Security\RandomGenerator;
33
use SilverStripe\Security\SecurityToken;
34
use SilverStripe\View\ArrayData;
35
use SilverStripe\View\SSViewer;
36
37
/**
38
 * @skipUpgrade
39
 */
40
class FormTest extends FunctionalTest
41
{
42
43
    protected static $fixture_file = 'FormTest.yml';
44
45
    protected static $extra_dataobjects = array(
46
        Player::class,
47
        Team::class,
48
    );
49
50
    protected static $extra_controllers = [
51
        TestController::class,
52
        ControllerWithSecurityToken::class,
53
        ControllerWithStrictPostCheck::class,
54
        ControllerWithSpecialSubmittedValueFields::class
55
    ];
56
57
    protected static $disable_themes = true;
58
59
    protected function setUp()
60
    {
61
        parent::setUp();
62
63
        // Suppress themes
64
        SSViewer::set_themes(
65
            [
66
            SSViewer::DEFAULT_THEME
67
            ]
68
        );
69
    }
70
71
    /**
72
     * @return array
73
     */
74
    public function boolDataProvider()
75
    {
76
        return [
77
            [false],
78
            [true],
79
        ];
80
    }
81
82
    public function testLoadDataFromRequest()
83
    {
84
        $form = new Form(
85
            Controller::curr(),
86
            'Form',
87
            new FieldList(
88
                new TextField('key1'),
89
                new TextField('namespace[key2]'),
90
                new TextField('namespace[key3][key4]'),
91
                new TextField('othernamespace[key5][key6][key7]')
92
            ),
93
            new FieldList()
94
        );
95
96
        // 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...
97
        $requestData = array(
98
            'key1' => 'val1',
99
            'namespace' => array(
100
                'key2' => 'val2',
101
                'key3' => array(
102
                    'key4' => 'val4',
103
                )
104
            ),
105
            'othernamespace' => array(
106
                'key5' => array(
107
                    'key6' =>array(
108
                        'key7' => 'val7'
109
                    )
110
                )
111
            )
112
        );
113
114
        $form->loadDataFrom($requestData);
115
116
        $fields = $form->Fields();
117
        $this->assertEquals('val1', $fields->fieldByName('key1')->Value());
118
        $this->assertEquals('val2', $fields->fieldByName('namespace[key2]')->Value());
119
        $this->assertEquals('val4', $fields->fieldByName('namespace[key3][key4]')->Value());
120
        $this->assertEquals('val7', $fields->fieldByName('othernamespace[key5][key6][key7]')->Value());
121
    }
122
123
    public function testSubmitReadonlyFields()
124
    {
125
        $this->get('FormTest_Controller');
126
127
        // Submitting a value for a readonly field should be ignored
128
        $response = $this->post(
129
            'FormTest_Controller/Form',
130
            array(
131
                'Email' => 'invalid',
132
                'Number' => '888',
133
                'ReadonlyField' => '<script>alert("hacxzored")</script>'
134
                // leaving out "Required" field
135
            )
136
        );
137
138
        // Number field updates its value
139
        $this->assertContains('<input type="text" name="Number" value="888"', $response->getBody());
140
141
142
        // Readonly field remains
143
        $this->assertContains(
144
            '<input type="text" name="ReadonlyField" value="This value is readonly"',
145
            $response->getBody()
146
        );
147
148
        $this->assertNotContains('hacxzored', $response->getBody());
149
    }
150
151
    public function testLoadDataFromUnchangedHandling()
152
    {
153
        $form = new Form(
154
            Controller::curr(),
155
            'Form',
156
            new FieldList(
157
                new TextField('key1'),
158
                new TextField('key2')
159
            ),
160
            new FieldList()
161
        );
162
        $form->loadDataFrom(
163
            array(
164
            'key1' => 'save',
165
            'key2' => 'dontsave',
166
            'key2_unchanged' => '1'
167
            )
168
        );
169
        $this->assertEquals(
170
            $form->getData(),
171
            array(
172
                'key1' => 'save',
173
                'key2' => null,
174
            ),
175
            'loadDataFrom() doesnt save a field if a matching "<fieldname>_unchanged" flag is set'
176
        );
177
    }
178
179
    public function testLoadDataFromObject()
180
    {
181
        $form = new Form(
182
            Controller::curr(),
183
            'Form',
184
            new FieldList(
185
                new HeaderField('MyPlayerHeader', 'My Player'),
186
                new TextField('Name'), // appears in both Player and Team
187
                new TextareaField('Biography'),
188
                new DateField('Birthday'),
189
                new NumericField('BirthdayYear') // dynamic property
190
            ),
191
            new FieldList()
192
        );
193
194
        $captainWithDetails = $this->objFromFixture(Player::class, 'captainWithDetails');
195
        $form->loadDataFrom($captainWithDetails);
196
        $this->assertEquals(
197
            $form->getData(),
198
            array(
199
                'Name' => 'Captain Details',
200
                'Biography' => 'Bio 1',
201
                'Birthday' => '1982-01-01',
202
                'BirthdayYear' => '1982',
203
            ),
204
            'LoadDataFrom() loads simple fields and dynamic getters'
205
        );
206
207
        $captainNoDetails = $this->objFromFixture(Player::class, 'captainNoDetails');
208
        $form->loadDataFrom($captainNoDetails);
209
        $this->assertEquals(
210
            $form->getData(),
211
            array(
212
                'Name' => 'Captain No Details',
213
                'Biography' => null,
214
                'Birthday' => null,
215
                'BirthdayYear' => 0,
216
            ),
217
            'LoadNonBlankDataFrom() loads only fields with values, and doesnt overwrite existing values'
218
        );
219
    }
220
221
    public function testLoadDataFromClearMissingFields()
222
    {
223
        $form = new Form(
224
            Controller::curr(),
225
            'Form',
226
            new FieldList(
227
                new HeaderField('MyPlayerHeader', 'My Player'),
228
                new TextField('Name'), // appears in both Player and Team
229
                new TextareaField('Biography'),
230
                new DateField('Birthday'),
231
                new NumericField('BirthdayYear'), // dynamic property
232
                $unrelatedField = new TextField('UnrelatedFormField')
233
                //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...
234
            ),
235
            new FieldList()
236
        );
237
        $unrelatedField->setValue("random value");
238
239
        $captainWithDetails = $this->objFromFixture(Player::class, 'captainWithDetails');
240
        $form->loadDataFrom($captainWithDetails);
241
        $this->assertEquals(
242
            $form->getData(),
243
            array(
244
                'Name' => 'Captain Details',
245
                'Biography' => 'Bio 1',
246
                'Birthday' => '1982-01-01',
247
                'BirthdayYear' => '1982',
248
                'UnrelatedFormField' => 'random value',
249
            ),
250
            'LoadDataFrom() doesnt overwrite fields not found in the object'
251
        );
252
253
        $captainWithDetails = $this->objFromFixture(Player::class, 'captainNoDetails');
254
        $team2 = $this->objFromFixture(Team::class, 'team2');
255
        $form->loadDataFrom($captainWithDetails);
256
        $form->loadDataFrom($team2, Form::MERGE_CLEAR_MISSING);
257
        $this->assertEquals(
258
            $form->getData(),
259
            array(
260
                'Name' => 'Team 2',
261
                'Biography' => '',
262
                'Birthday' => '',
263
                'BirthdayYear' => 0,
264
                'UnrelatedFormField' => null,
265
            ),
266
            'LoadDataFrom() overwrites fields not found in the object with $clearMissingFields=true'
267
        );
268
    }
269
270
    public function testLoadDataFromWithForceSetValueFlag()
271
    {
272
        // Get our data formatted in internal value and in submitted value
273
        // We're using very esoteric date and time format
274
        $dataInSubmittedValue = [
275
            'SomeDateTimeField' => 'Fri, Jun 15, \'18 17:28:05',
276
            'SomeTimeField' => '05 o\'clock PM 28 05'
277
        ];
278
        $dataInInternalValue = [
279
            'SomeDateTimeField' => '2018-06-15 17:28:05',
280
            'SomeTimeField' => '17:28:05'
281
        ];
282
283
        // Test loading our data with the MERGE_AS_INTERNAL_VALUE
284
        $form = $this->getStubFormWithWeirdValueFormat();
285
        $form->loadDataFrom($dataInInternalValue, Form::MERGE_AS_INTERNAL_VALUE);
286
287
        $this->assertEquals(
288
            $dataInInternalValue,
289
            $form->getData()
290
        );
291
292
        // Test loading our data with the MERGE_AS_SUBMITTED_VALUE and an data passed as an object
293
        $form = $this->getStubFormWithWeirdValueFormat();
294
        $form->loadDataFrom(ArrayData::create($dataInSubmittedValue), Form::MERGE_AS_SUBMITTED_VALUE);
295
        $this->assertEquals(
296
            $dataInInternalValue,
297
            $form->getData()
298
        );
299
300
        // Test loading our data without the MERGE_AS_INTERNAL_VALUE and without MERGE_AS_SUBMITTED_VALUE
301
        $form = $this->getStubFormWithWeirdValueFormat();
302
        $form->loadDataFrom($dataInSubmittedValue);
303
304
        $this->assertEquals(
305
            $dataInInternalValue,
306
            $form->getData()
307
        );
308
    }
309
310
    public function testLookupFieldDisabledSaving()
311
    {
312
        $object = new Team();
313
        $form = new Form(
314
            Controller::curr(),
315
            'Form',
316
            new FieldList(
317
                new LookupField('Players', 'Players')
318
            ),
319
            new FieldList()
320
        );
321
        $form->loadDataFrom(
322
            array(
323
            'Players' => array(
324
                    14,
325
                    18,
326
                    22
327
                ),
328
            )
329
        );
330
        $form->saveInto($object);
331
        $playersIds = $object->Players()->getIDList();
332
333
        $this->assertTrue($form->validationResult()->isValid());
334
        $this->assertEquals(
335
            $playersIds,
336
            array(),
337
            'saveInto() should not save into the DataObject for the LookupField'
338
        );
339
    }
340
341
    public function testLoadDataFromIgnoreFalseish()
342
    {
343
        $form = new Form(
344
            Controller::curr(),
345
            'Form',
346
            new FieldList(
347
                new TextField('Biography', 'Biography', 'Custom Default')
348
            ),
349
            new FieldList()
350
        );
351
352
        $captainNoDetails = $this->objFromFixture(Player::class, 'captainNoDetails');
353
        $captainWithDetails = $this->objFromFixture(Player::class, 'captainWithDetails');
354
355
        $form->loadDataFrom($captainNoDetails, Form::MERGE_IGNORE_FALSEISH);
356
        $this->assertEquals(
357
            $form->getData(),
358
            array('Biography' => 'Custom Default'),
359
            'LoadDataFrom() doesn\'t overwrite fields when MERGE_IGNORE_FALSEISH set and values are false-ish'
360
        );
361
362
        $form->loadDataFrom($captainWithDetails, Form::MERGE_IGNORE_FALSEISH);
363
        $this->assertEquals(
364
            $form->getData(),
365
            array('Biography' => 'Bio 1'),
366
            'LoadDataFrom() does overwrite fields when MERGE_IGNORE_FALSEISH set and values arent false-ish'
367
        );
368
    }
369
370
    public function testFormMethodOverride()
371
    {
372
        $form = $this->getStubForm();
373
        $form->setFormMethod('GET');
374
        $this->assertNull($form->Fields()->dataFieldByName('_method'));
375
376
        $form = $this->getStubForm();
377
        $form->setFormMethod('PUT');
378
        $this->assertEquals(
379
            $form->Fields()->dataFieldByName('_method')->Value(),
380
            'PUT',
381
            'PUT override in forms has PUT in hiddenfield'
382
        );
383
        $this->assertEquals(
384
            $form->FormMethod(),
385
            'POST',
386
            'PUT override in forms has POST in <form> tag'
387
        );
388
389
        $form = $this->getStubForm();
390
        $form->setFormMethod('DELETE');
391
        $this->assertEquals(
392
            $form->Fields()->dataFieldByName('_method')->Value(),
393
            'DELETE',
394
            'PUT override in forms has PUT in hiddenfield'
395
        );
396
        $this->assertEquals(
397
            $form->FormMethod(),
398
            'POST',
399
            'PUT override in forms has POST in <form> tag'
400
        );
401
    }
402
403
    public function testValidationExemptActions()
404
    {
405
        $this->get('FormTest_Controller');
406
407
        $this->submitForm(
408
            'Form_Form',
409
            'action_doSubmit',
410
            array(
411
                'Email' => '[email protected]'
412
            )
413
        );
414
415
        // Firstly, assert that required fields still work when not using an exempt action
416
        $this->assertPartialMatchBySelector(
417
            '#Form_Form_SomeRequiredField_Holder .required',
418
            array('"Some Required Field" is required'),
419
            'Required fields show a notification on field when left blank'
420
        );
421
422
        // Re-submit the form using validation-exempt button
423
        $this->submitForm(
424
            'Form_Form',
425
            'action_doSubmitValidationExempt',
426
            array(
427
                'Email' => '[email protected]'
428
            )
429
        );
430
431
        // The required message should be empty if validation was skipped
432
        $items = $this->cssParser()->getBySelector('#Form_Form_SomeRequiredField_Holder .required');
433
        $this->assertEmpty($items);
434
435
        // And the session message should show up is submitted successfully
436
        $this->assertPartialMatchBySelector(
437
            '#Form_Form_error',
438
            array(
439
                'Validation skipped'
440
            ),
441
            'Form->sessionMessage() shows up after reloading the form'
442
        );
443
444
        // Test this same behaviour, but with a form-action exempted via instance
445
        $this->submitForm(
446
            'Form_Form',
447
            'action_doSubmitActionExempt',
448
            array(
449
                'Email' => '[email protected]'
450
            )
451
        );
452
453
        // The required message should be empty if validation was skipped
454
        $items = $this->cssParser()->getBySelector('#Form_Form_SomeRequiredField_Holder .required');
455
        $this->assertEmpty($items);
456
457
        // And the session message should show up is submitted successfully
458
        $this->assertPartialMatchBySelector(
459
            '#Form_Form_error',
460
            array(
461
                'Validation bypassed!'
462
            ),
463
            'Form->sessionMessage() shows up after reloading the form'
464
        );
465
    }
466
467
    public function testSessionValidationMessage()
468
    {
469
        $this->get('FormTest_Controller');
470
471
        $response = $this->post(
472
            'FormTest_Controller/Form',
473
            array(
474
                'Email' => 'invalid',
475
                'Number' => '<a href="http://mysite.com">link</a>' // XSS attempt
476
                // leaving out "Required" field
477
            )
478
        );
479
480
        $this->assertPartialMatchBySelector(
481
            '#Form_Form_Email_Holder span.message',
482
            array(
483
                'Please enter an email address'
484
            ),
485
            'Formfield validation shows note on field if invalid'
486
        );
487
        $this->assertPartialMatchBySelector(
488
            '#Form_Form_SomeRequiredField_Holder span.required',
489
            array(
490
                '"Some Required Field" is required'
491
            ),
492
            'Required fields show a notification on field when left blank'
493
        );
494
495
        $this->assertContains(
496
            '&#039;&lt;a href=&quot;http://mysite.com&quot;&gt;link&lt;/a&gt;&#039; is not a number, only '
497
            . 'numbers can be accepted for this field',
498
            $response->getBody(),
499
            "Validation messages are safely XML encoded"
500
        );
501
        $this->assertNotContains(
502
            '<a href="http://mysite.com">link</a>',
503
            $response->getBody(),
504
            "Unsafe content is not emitted directly inside the response body"
505
        );
506
    }
507
508
    public function testSessionSuccessMessage()
509
    {
510
        $this->get('FormTest_Controller');
511
512
        $this->post(
513
            'FormTest_Controller/Form',
514
            array(
515
                'Email' => '[email protected]',
516
                'SomeRequiredField' => 'test',
517
            )
518
        );
519
        $this->assertPartialMatchBySelector(
520
            '#Form_Form_error',
521
            array(
522
                'Test save was successful'
523
            ),
524
            'Form->sessionMessage() shows up after reloading the form'
525
        );
526
    }
527
528
    public function testValidationException()
529
    {
530
        $this->get('FormTest_Controller');
531
532
        $this->post(
533
            'FormTest_Controller/Form',
534
            array(
535
                'Email' => '[email protected]',
536
                'SomeRequiredField' => 'test',
537
                'action_doTriggerException' => 1,
538
            )
539
        );
540
        $this->assertPartialMatchBySelector(
541
            '#Form_Form_Email_Holder span.message',
542
            array(
543
                'Error on Email field'
544
            ),
545
            'Formfield validation shows note on field if invalid'
546
        );
547
        $this->assertPartialMatchBySelector(
548
            '#Form_Form_error',
549
            array(
550
                'Error at top of form'
551
            ),
552
            'Required fields show a notification on field when left blank'
553
        );
554
    }
555
556
    public function testGloballyDisabledSecurityTokenInheritsToNewForm()
557
    {
558
        SecurityToken::enable();
559
560
        $form1 = $this->getStubForm();
561
        $this->assertInstanceOf(SecurityToken::class, $form1->getSecurityToken());
562
563
        SecurityToken::disable();
564
565
        $form2 = $this->getStubForm();
566
        $this->assertInstanceOf(NullSecurityToken::class, $form2->getSecurityToken());
567
568
        SecurityToken::enable();
569
    }
570
571
    public function testDisableSecurityTokenDoesntAddTokenFormField()
572
    {
573
        SecurityToken::enable();
574
575
        $formWithToken = $this->getStubForm();
576
        $this->assertInstanceOf(
577
            'SilverStripe\\Forms\\HiddenField',
578
            $formWithToken->Fields()->fieldByName(SecurityToken::get_default_name()),
579
            'Token field added by default'
580
        );
581
582
        $formWithoutToken = $this->getStubForm();
583
        $formWithoutToken->disableSecurityToken();
584
        $this->assertNull(
585
            $formWithoutToken->Fields()->fieldByName(SecurityToken::get_default_name()),
586
            'Token field not added if disableSecurityToken() is set'
587
        );
588
    }
589
590
    public function testDisableSecurityTokenAcceptsSubmissionWithoutToken()
591
    {
592
        SecurityToken::enable();
593
        $expectedToken = SecurityToken::inst()->getValue();
594
595
        $this->get('FormTest_ControllerWithSecurityToken');
596
        // can't use submitForm() as it'll automatically insert SecurityID into the POST data
597
        $response = $this->post(
598
            'FormTest_ControllerWithSecurityToken/Form',
599
            array(
600
                'Email' => '[email protected]',
601
                'action_doSubmit' => 1
602
                // leaving out security token
603
            )
604
        );
605
        $this->assertEquals(400, $response->getStatusCode(), 'Submission fails without security token');
606
607
        // Generate a new token which doesn't match the current one
608
        $generator = new RandomGenerator();
609
        $invalidToken = $generator->randomToken('sha1');
610
        $this->assertNotEquals($invalidToken, $expectedToken);
611
612
        // Test token with request
613
        $this->get('FormTest_ControllerWithSecurityToken');
614
        $response = $this->post(
615
            'FormTest_ControllerWithSecurityToken/Form',
616
            array(
617
                'Email' => '[email protected]',
618
                'action_doSubmit' => 1,
619
                'SecurityID' => $invalidToken
620
            )
621
        );
622
        $this->assertEquals(200, $response->getStatusCode(), 'Submission reloads form if security token invalid');
623
        $this->assertTrue(
624
            stripos($response->getBody(), 'name="SecurityID" value="' . $expectedToken . '"') !== false,
625
            'Submission reloads with correct security token after failure'
626
        );
627
        $this->assertTrue(
628
            stripos($response->getBody(), 'name="SecurityID" value="' . $invalidToken . '"') === false,
629
            'Submission reloads without incorrect security token after failure'
630
        );
631
632
        $matched = $this->cssParser()->getBySelector('#Form_Form_Email');
633
        $attrs = $matched[0]->attributes();
634
        $this->assertEquals('[email protected]', (string)$attrs['value'], 'Submitted data is preserved');
635
636
        $this->get('FormTest_ControllerWithSecurityToken');
637
        $tokenEls = $this->cssParser()->getBySelector('#Form_Form_SecurityID');
638
        $this->assertEquals(
639
            1,
640
            count($tokenEls),
641
            'Token form field added for controller without disableSecurityToken()'
642
        );
643
        $token = (string)$tokenEls[0];
644
        $response = $this->submitForm(
645
            'Form_Form',
646
            null,
647
            array(
648
                'Email' => '[email protected]',
649
                'SecurityID' => $token
650
            )
651
        );
652
        $this->assertEquals(200, $response->getStatusCode(), 'Submission suceeds with security token');
653
    }
654
655
    public function testStrictFormMethodChecking()
656
    {
657
        $this->get('FormTest_ControllerWithStrictPostCheck');
658
        $response = $this->get(
659
            'FormTest_ControllerWithStrictPostCheck/Form/[email protected]&action_doSubmit=1'
660
        );
661
        $this->assertEquals(405, $response->getStatusCode(), 'Submission fails with wrong method');
662
663
        $this->get('FormTest_ControllerWithStrictPostCheck');
664
        $response = $this->post(
665
            'FormTest_ControllerWithStrictPostCheck/Form',
666
            array(
667
                'Email' => '[email protected]',
668
                'action_doSubmit' => 1
669
            )
670
        );
671
        $this->assertEquals(200, $response->getStatusCode(), 'Submission succeeds with correct method');
672
    }
673
674
    public function testEnableSecurityToken()
675
    {
676
        SecurityToken::disable();
677
        $form = $this->getStubForm();
678
        $this->assertFalse($form->getSecurityToken()->isEnabled());
679
        $form->enableSecurityToken();
680
        $this->assertTrue($form->getSecurityToken()->isEnabled());
681
682
        SecurityToken::disable(); // restore original
683
    }
684
685
    public function testDisableSecurityToken()
686
    {
687
        SecurityToken::enable();
688
        $form = $this->getStubForm();
689
        $this->assertTrue($form->getSecurityToken()->isEnabled());
690
        $form->disableSecurityToken();
691
        $this->assertFalse($form->getSecurityToken()->isEnabled());
692
693
        SecurityToken::disable(); // restore original
694
    }
695
696
    public function testEncType()
697
    {
698
        $form = $this->getStubForm();
699
        $this->assertEquals('application/x-www-form-urlencoded', $form->getEncType());
700
701
        $form->setEncType(Form::ENC_TYPE_MULTIPART);
702
        $this->assertEquals('multipart/form-data', $form->getEncType());
703
704
        $form = $this->getStubForm();
705
        $form->Fields()->push(new FileField(null));
706
        $this->assertEquals('multipart/form-data', $form->getEncType());
707
708
        $form->setEncType(Form::ENC_TYPE_URLENCODED);
709
        $this->assertEquals('application/x-www-form-urlencoded', $form->getEncType());
710
    }
711
712
    public function testAddExtraClass()
713
    {
714
        $form = $this->getStubForm();
715
        $form->addExtraClass('class1');
716
        $form->addExtraClass('class2');
717
        $this->assertStringEndsWith('class1 class2', $form->extraClass());
718
    }
719
720
    public function testRemoveExtraClass()
721
    {
722
        $form = $this->getStubForm();
723
        $form->addExtraClass('class1');
724
        $form->addExtraClass('class2');
725
        $this->assertStringEndsWith('class1 class2', $form->extraClass());
726
        $form->removeExtraClass('class1');
727
        $this->assertStringEndsWith('class2', $form->extraClass());
728
    }
729
730
    public function testAddManyExtraClasses()
731
    {
732
        $form = $this->getStubForm();
733
        //test we can split by a range of spaces and tabs
734
        $form->addExtraClass('class1 class2     class3	class4		class5');
735
        $this->assertStringEndsWith(
736
            'class1 class2 class3 class4 class5',
737
            $form->extraClass()
738
        );
739
        //test that duplicate classes don't get added
740
        $form->addExtraClass('class1 class2');
741
        $this->assertStringEndsWith(
742
            'class1 class2 class3 class4 class5',
743
            $form->extraClass()
744
        );
745
    }
746
747
    public function testRemoveManyExtraClasses()
748
    {
749
        $form = $this->getStubForm();
750
        $form->addExtraClass('class1 class2     class3	class4		class5');
751
        //test we can remove a single class we just added
752
        $form->removeExtraClass('class3');
753
        $this->assertStringEndsWith(
754
            'class1 class2 class4 class5',
755
            $form->extraClass()
756
        );
757
        //check we can remove many classes at once
758
        $form->removeExtraClass('class1 class5');
759
        $this->assertStringEndsWith(
760
            'class2 class4',
761
            $form->extraClass()
762
        );
763
        //check that removing a dud class is fine
764
        $form->removeExtraClass('dudClass');
765
        $this->assertStringEndsWith(
766
            'class2 class4',
767
            $form->extraClass()
768
        );
769
    }
770
771
    public function testDefaultClasses()
772
    {
773
        Form::config()->update(
774
            'default_classes',
775
            array(
776
            'class1',
777
            )
778
        );
779
780
        $form = $this->getStubForm();
781
782
        $this->assertContains('class1', $form->extraClass(), 'Class list does not contain expected class');
783
784
        Form::config()->update(
785
            'default_classes',
786
            array(
787
            'class1',
788
            'class2',
789
            )
790
        );
791
792
        $form = $this->getStubForm();
793
794
        $this->assertContains('class1 class2', $form->extraClass(), 'Class list does not contain expected class');
795
796
        Form::config()->update(
797
            'default_classes',
798
            array(
799
            'class3',
800
            )
801
        );
802
803
        $form = $this->getStubForm();
804
805
        $this->assertContains('class3', $form->extraClass(), 'Class list does not contain expected class');
806
807
        $form->removeExtraClass('class3');
808
809
        $this->assertNotContains('class3', $form->extraClass(), 'Class list contains unexpected class');
810
    }
811
812
    public function testAttributes()
813
    {
814
        $form = $this->getStubForm();
815
        $form->setAttribute('foo', 'bar');
816
        $this->assertEquals('bar', $form->getAttribute('foo'));
817
        $attrs = $form->getAttributes();
818
        $this->assertArrayHasKey('foo', $attrs);
819
        $this->assertEquals('bar', $attrs['foo']);
820
    }
821
822
    /**
823
     * @skipUpgrade
824
     */
825
    public function testButtonClicked()
826
    {
827
        $form = $this->getStubForm();
828
        $action = $form->getRequestHandler()->buttonClicked();
829
        $this->assertNull($action);
830
831
        $controller = new FormTest\TestController();
832
        $form = $controller->Form();
833
        $request = new HTTPRequest(
834
            'POST',
835
            'FormTest_Controller/Form',
836
            array(),
837
            array(
838
            'Email' => '[email protected]',
839
            'SomeRequiredField' => 1,
840
            'action_doSubmit' => 1
841
            )
842
        );
843
        $request->setSession(new Session([]));
844
845
        $form->getRequestHandler()->httpSubmission($request);
846
        $button = $form->getRequestHandler()->buttonClicked();
847
        $this->assertInstanceOf(FormAction::class, $button);
848
        $this->assertEquals('doSubmit', $button->actionName());
849
        $form = new Form(
850
            $controller,
851
            'Form',
852
            new FieldList(new FormAction('doSubmit', 'Inline action')),
853
            new FieldList()
854
        );
855
        $form->disableSecurityToken();
856
        $request = new HTTPRequest(
857
            'POST',
858
            'FormTest_Controller/Form',
859
            array(),
860
            array(
861
            'action_doSubmit' => 1
862
            )
863
        );
864
        $request->setSession(new Session([]));
865
866
        $form->getRequestHandler()->httpSubmission($request);
867
        $button = $form->getRequestHandler()->buttonClicked();
868
        $this->assertInstanceOf(FormAction::class, $button);
869
        $this->assertEquals('doSubmit', $button->actionName());
870
    }
871
872
    public function testCheckAccessAction()
873
    {
874
        $controller = new FormTest\TestController();
875
        $form = new Form(
876
            $controller,
877
            'Form',
878
            new FieldList(),
879
            new FieldList(new FormAction('actionName', 'Action'))
880
        );
881
        $this->assertTrue($form->getRequestHandler()->checkAccessAction('actionName'));
882
883
        $form = new Form(
884
            $controller,
885
            'Form',
886
            new FieldList(new FormAction('inlineAction', 'Inline action')),
887
            new FieldList()
888
        );
889
        $this->assertTrue($form->getRequestHandler()->checkAccessAction('inlineAction'));
890
    }
891
892
    public function testAttributesHTML()
893
    {
894
        $form = $this->getStubForm();
895
896
        $form->setAttribute('foo', 'bar');
897
        $this->assertContains('foo="bar"', $form->getAttributesHTML());
898
899
        $form->setAttribute('foo', null);
900
        $this->assertNotContains('foo="bar"', $form->getAttributesHTML());
901
902
        $form->setAttribute('foo', true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $value of SilverStripe\Forms\Form::setAttribute(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

902
        $form->setAttribute('foo', /** @scrutinizer ignore-type */ true);
Loading history...
903
        $this->assertContains('foo="foo"', $form->getAttributesHTML());
904
905
        $form->setAttribute('one', 1);
906
        $form->setAttribute('two', 2);
907
        $form->setAttribute('three', 3);
908
        $this->assertNotContains('one="1"', $form->getAttributesHTML('one', 'two'));
909
        $this->assertNotContains('two="2"', $form->getAttributesHTML('one', 'two'));
910
        $this->assertContains('three="3"', $form->getAttributesHTML('one', 'two'));
911
    }
912
913
    public function testMessageEscapeHtml()
914
    {
915
        $form = $this->getStubForm();
916
        $form->setMessage('<em>Escaped HTML</em>', 'good', ValidationResult::CAST_TEXT);
917
        $parser = new CSSContentParser($form->forTemplate());
918
        $messageEls = $parser->getBySelector('.message');
919
        $this->assertContains(
920
            '&lt;em&gt;Escaped HTML&lt;/em&gt;',
921
            $messageEls[0]->asXML()
922
        );
923
924
        $form = $this->getStubForm();
925
        $form->setMessage('<em>Unescaped HTML</em>', 'good', ValidationResult::CAST_HTML);
926
        $parser = new CSSContentParser($form->forTemplate());
927
        $messageEls = $parser->getBySelector('.message');
928
        $this->assertContains(
929
            '<em>Unescaped HTML</em>',
930
            $messageEls[0]->asXML()
931
        );
932
    }
933
934
    public function testFieldMessageEscapeHtml()
935
    {
936
        $form = $this->getStubForm();
937
        $form->Fields()->dataFieldByName('key1')->setMessage('<em>Escaped HTML</em>', 'good');
938
        $parser = new CSSContentParser($result = $form->forTemplate());
939
        $messageEls = $parser->getBySelector('#Form_Form_key1_Holder .message');
940
        $this->assertContains(
941
            '&lt;em&gt;Escaped HTML&lt;/em&gt;',
942
            $messageEls[0]->asXML()
943
        );
944
945
        // Test with HTML
946
        $form = $this->getStubForm();
947
        $form
948
            ->Fields()
949
            ->dataFieldByName('key1')
950
            ->setMessage('<em>Unescaped HTML</em>', 'good', ValidationResult::CAST_HTML);
951
        $parser = new CSSContentParser($form->forTemplate());
952
        $messageEls = $parser->getBySelector('#Form_Form_key1_Holder .message');
953
        $this->assertContains(
954
            '<em>Unescaped HTML</em>',
955
            $messageEls[0]->asXML()
956
        );
957
    }
958
959
    public function testGetExtraFields()
960
    {
961
        $form = new FormTest\ExtraFieldsForm(
962
            new FormTest\TestController(),
963
            'Form',
964
            new FieldList(new TextField('key1')),
965
            new FieldList()
966
        );
967
968
        $data = array(
969
            'key1' => 'test',
970
            'ExtraFieldCheckbox' => false,
971
        );
972
973
        $form->loadDataFrom($data);
974
975
        $formData = $form->getData();
976
        $this->assertEmpty($formData['ExtraFieldCheckbox']);
977
    }
978
979
    /**
980
     * @dataProvider boolDataProvider
981
     * @param bool $allow
982
     */
983
    public function testPasswordPostback($allow)
984
    {
985
        $form = $this->getStubForm();
986
        $form->enableSecurityToken();
987
        $form->Fields()->push(
988
            PasswordField::create('Password')
989
                ->setAllowValuePostback($allow)
990
        );
991
        $form->Actions()->push(FormAction::create('doSubmit'));
992
        $request = new HTTPRequest(
993
            'POST',
994
            'FormTest_Controller/Form',
995
            [],
996
            [
997
                'key1' => 'foo',
998
                'Password' => 'hidden',
999
                SecurityToken::inst()->getName() => 'fail',
1000
                'action_doSubmit' => 1,
1001
            ]
1002
        );
1003
        $form->getRequestHandler()->httpSubmission($request);
1004
        $parser = new CSSContentParser($form->forTemplate());
1005
        $passwords = $parser->getBySelector('input#Password');
1006
        $this->assertNotNull($passwords);
1007
        $this->assertCount(1, $passwords);
1008
        /* @var \SimpleXMLElement $password */
1009
        $password = $passwords[0];
1010
        $attrs = iterator_to_array($password->attributes());
1011
        if ($allow) {
1012
            $this->assertArrayHasKey('value', $attrs);
1013
            $this->assertEquals('hidden', $attrs['value']);
1014
        } else {
1015
            $this->assertArrayNotHasKey('value', $attrs);
1016
        }
1017
    }
1018
1019
    /**
1020
     * This test confirms that when a form validation fails, the submitted value are stored in the session and are
1021
     * reloaded correctly once the form is re-rendered. This indirectly test `Form::restoreFormState`,
1022
     * `Form::setSessionData`, `Form::getSessionData` and `Form::clearFormState`.
1023
     */
1024
    public function testRestoreFromState()
1025
    {
1026
        // Use a specially crafted controlled for this request. The associated form contains fields that override the
1027
        // `setSubmittedValue` and require an internal format that differs from the submitted format.
1028
        $this->get('FormTest_ControllerWithSpecialSubmittedValueFields')->getBody();
1029
1030
        // Posting our form. This should fail and redirect us to the form page and preload our submit value
1031
        $response = $this->post(
1032
            'FormTest_ControllerWithSpecialSubmittedValueFields/Form',
1033
            array(
1034
                'SomeDateField' => '15/06/2018',
1035
                'SomeFrenchNumericField' => '9 876,5432',
1036
                'SomeFrenchMoneyField' => [
1037
                    'Amount' => '9 876,54',
1038
                    'Currency' => 'NZD'
1039
                ]
1040
                // Validation will fail because we leave out SomeRequiredField
1041
            ),
1042
            []
1043
        );
1044
1045
        // Test our reloaded form field
1046
        $body = $response->getBody();
1047
        $this->assertContains(
1048
            '<input type="text" name="SomeDateField" value="15/06/2018"',
1049
            $body,
1050
            'Our reloaded form should contain a SomeDateField with the value "15/06/2018"'
1051
        );
1052
1053
        $this->assertContains(
1054
            '<input type="text" name="SomeFrenchNumericField" value="9 876,5432" ',
1055
            $body,
1056
            'Our reloaded form should contain a SomeFrenchNumericField with the value "9 876,5432"'
1057
        );
1058
1059
        $this->assertContains(
1060
            '<input type="text" name="SomeFrenchMoneyField[Currency]" value="NZD"',
1061
            $body,
1062
            'Our reloaded form should contain a SomeFrenchMoneyField[Currency] with the value "NZD"'
1063
        );
1064
1065
        $this->assertContains(
1066
            '<input type="text" name="SomeFrenchMoneyField[Amount]" value="9 876,54" ',
1067
            $body,
1068
            'Our reloaded form should contain a SomeFrenchMoneyField[Amount] with the value "9 876,54"'
1069
        );
1070
1071
        $this->assertEmpty(
1072
            $this->mainSession->session()->get('FormInfo.Form_Form'),
1073
            'Our form was reloaded successfully. That should have cleared our session.'
1074
        );
1075
    }
1076
1077
    protected function getStubForm()
1078
    {
1079
        return new Form(
1080
            new FormTest\TestController(),
1081
            'Form',
1082
            new FieldList(new TextField('key1')),
1083
            new FieldList()
1084
        );
1085
    }
1086
1087
    /**
1088
     * Some fields handle submitted values differently from their internal values. This forms contains 2 such fields
1089
     * * a SomeDateTimeField that expect a date such as `Fri, Jun 15, '18 17:28:05`,
1090
     * * a SomeTimeField that expects it's time as `05 o'clock PM 28 05`
1091
     *
1092
     * @return Form
1093
     */
1094
    protected function getStubFormWithWeirdValueFormat()
1095
    {
1096
        return new Form(
1097
            Controller::curr(),
1098
            'Form',
1099
            new FieldList(
1100
                $dateField = DatetimeField::create('SomeDateTimeField')
1101
                    ->setHTML5(false)
1102
                    ->setDatetimeFormat("EEE, MMM d, ''yy HH:mm:ss"),
1103
                $timeField = TimeField::create('SomeTimeField')
1104
                    ->setHTML5(false)
1105
                    ->setTimeFormat("hh 'o''clock' a mm ss") // Swatch Internet Time format
1106
            ),
1107
            new FieldList()
1108
        );
1109
    }
1110
}
1111