Passed
Push — 4 ( 39a86e...82634b )
by Damian
16:13 queued 07:21
created

FormTest::getStubFormWithWeirdValueFormat()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 0
dl 0
loc 14
rs 9.4285
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 numbers can be accepted for this field',
497
            $response->getBody(),
498
            "Validation messages are safely XML encoded"
499
        );
500
        $this->assertNotContains(
501
            '<a href="http://mysite.com">link</a>',
502
            $response->getBody(),
503
            "Unsafe content is not emitted directly inside the response body"
504
        );
505
    }
506
507
    public function testSessionSuccessMessage()
508
    {
509
        $this->get('FormTest_Controller');
510
511
        $this->post(
512
            'FormTest_Controller/Form',
513
            array(
514
                'Email' => '[email protected]',
515
                'SomeRequiredField' => 'test',
516
            )
517
        );
518
        $this->assertPartialMatchBySelector(
519
            '#Form_Form_error',
520
            array(
521
                'Test save was successful'
522
            ),
523
            'Form->sessionMessage() shows up after reloading the form'
524
        );
525
    }
526
527
    public function testValidationException()
528
    {
529
        $this->get('FormTest_Controller');
530
531
        $this->post(
532
            'FormTest_Controller/Form',
533
            array(
534
                'Email' => '[email protected]',
535
                'SomeRequiredField' => 'test',
536
                'action_doTriggerException' => 1,
537
            )
538
        );
539
        $this->assertPartialMatchBySelector(
540
            '#Form_Form_Email_Holder span.message',
541
            array(
542
                'Error on Email field'
543
            ),
544
            'Formfield validation shows note on field if invalid'
545
        );
546
        $this->assertPartialMatchBySelector(
547
            '#Form_Form_error',
548
            array(
549
                'Error at top of form'
550
            ),
551
            'Required fields show a notification on field when left blank'
552
        );
553
    }
554
555
    public function testGloballyDisabledSecurityTokenInheritsToNewForm()
556
    {
557
        SecurityToken::enable();
558
559
        $form1 = $this->getStubForm();
560
        $this->assertInstanceOf(SecurityToken::class, $form1->getSecurityToken());
561
562
        SecurityToken::disable();
563
564
        $form2 = $this->getStubForm();
565
        $this->assertInstanceOf(NullSecurityToken::class, $form2->getSecurityToken());
566
567
        SecurityToken::enable();
568
    }
569
570
    public function testDisableSecurityTokenDoesntAddTokenFormField()
571
    {
572
        SecurityToken::enable();
573
574
        $formWithToken = $this->getStubForm();
575
        $this->assertInstanceOf(
576
            'SilverStripe\\Forms\\HiddenField',
577
            $formWithToken->Fields()->fieldByName(SecurityToken::get_default_name()),
578
            'Token field added by default'
579
        );
580
581
        $formWithoutToken = $this->getStubForm();
582
        $formWithoutToken->disableSecurityToken();
583
        $this->assertNull(
584
            $formWithoutToken->Fields()->fieldByName(SecurityToken::get_default_name()),
585
            'Token field not added if disableSecurityToken() is set'
586
        );
587
    }
588
589
    public function testDisableSecurityTokenAcceptsSubmissionWithoutToken()
590
    {
591
        SecurityToken::enable();
592
        $expectedToken = SecurityToken::inst()->getValue();
593
594
        $this->get('FormTest_ControllerWithSecurityToken');
595
        // can't use submitForm() as it'll automatically insert SecurityID into the POST data
596
        $response = $this->post(
597
            'FormTest_ControllerWithSecurityToken/Form',
598
            array(
599
                'Email' => '[email protected]',
600
                'action_doSubmit' => 1
601
                // leaving out security token
602
            )
603
        );
604
        $this->assertEquals(400, $response->getStatusCode(), 'Submission fails without security token');
605
606
        // Generate a new token which doesn't match the current one
607
        $generator = new RandomGenerator();
608
        $invalidToken = $generator->randomToken('sha1');
609
        $this->assertNotEquals($invalidToken, $expectedToken);
610
611
        // Test token with request
612
        $this->get('FormTest_ControllerWithSecurityToken');
613
        $response = $this->post(
614
            'FormTest_ControllerWithSecurityToken/Form',
615
            array(
616
                'Email' => '[email protected]',
617
                'action_doSubmit' => 1,
618
                'SecurityID' => $invalidToken
619
            )
620
        );
621
        $this->assertEquals(200, $response->getStatusCode(), 'Submission reloads form if security token invalid');
622
        $this->assertTrue(
623
            stripos($response->getBody(), 'name="SecurityID" value="' . $expectedToken . '"') !== false,
624
            'Submission reloads with correct security token after failure'
625
        );
626
        $this->assertTrue(
627
            stripos($response->getBody(), 'name="SecurityID" value="' . $invalidToken . '"') === false,
628
            'Submission reloads without incorrect security token after failure'
629
        );
630
631
        $matched = $this->cssParser()->getBySelector('#Form_Form_Email');
632
        $attrs = $matched[0]->attributes();
633
        $this->assertEquals('[email protected]', (string)$attrs['value'], 'Submitted data is preserved');
634
635
        $this->get('FormTest_ControllerWithSecurityToken');
636
        $tokenEls = $this->cssParser()->getBySelector('#Form_Form_SecurityID');
637
        $this->assertEquals(
638
            1,
639
            count($tokenEls),
640
            'Token form field added for controller without disableSecurityToken()'
641
        );
642
        $token = (string)$tokenEls[0];
643
        $response = $this->submitForm(
644
            'Form_Form',
645
            null,
646
            array(
647
                'Email' => '[email protected]',
648
                'SecurityID' => $token
649
            )
650
        );
651
        $this->assertEquals(200, $response->getStatusCode(), 'Submission suceeds with security token');
652
    }
653
654
    public function testStrictFormMethodChecking()
655
    {
656
        $this->get('FormTest_ControllerWithStrictPostCheck');
657
        $response = $this->get(
658
            'FormTest_ControllerWithStrictPostCheck/Form/[email protected]&action_doSubmit=1'
659
        );
660
        $this->assertEquals(405, $response->getStatusCode(), 'Submission fails with wrong method');
661
662
        $this->get('FormTest_ControllerWithStrictPostCheck');
663
        $response = $this->post(
664
            'FormTest_ControllerWithStrictPostCheck/Form',
665
            array(
666
                'Email' => '[email protected]',
667
                'action_doSubmit' => 1
668
            )
669
        );
670
        $this->assertEquals(200, $response->getStatusCode(), 'Submission succeeds with correct method');
671
    }
672
673
    public function testEnableSecurityToken()
674
    {
675
        SecurityToken::disable();
676
        $form = $this->getStubForm();
677
        $this->assertFalse($form->getSecurityToken()->isEnabled());
678
        $form->enableSecurityToken();
679
        $this->assertTrue($form->getSecurityToken()->isEnabled());
680
681
        SecurityToken::disable(); // restore original
682
    }
683
684
    public function testDisableSecurityToken()
685
    {
686
        SecurityToken::enable();
687
        $form = $this->getStubForm();
688
        $this->assertTrue($form->getSecurityToken()->isEnabled());
689
        $form->disableSecurityToken();
690
        $this->assertFalse($form->getSecurityToken()->isEnabled());
691
692
        SecurityToken::disable(); // restore original
693
    }
694
695
    public function testEncType()
696
    {
697
        $form = $this->getStubForm();
698
        $this->assertEquals('application/x-www-form-urlencoded', $form->getEncType());
699
700
        $form->setEncType(Form::ENC_TYPE_MULTIPART);
701
        $this->assertEquals('multipart/form-data', $form->getEncType());
702
703
        $form = $this->getStubForm();
704
        $form->Fields()->push(new FileField(null));
705
        $this->assertEquals('multipart/form-data', $form->getEncType());
706
707
        $form->setEncType(Form::ENC_TYPE_URLENCODED);
708
        $this->assertEquals('application/x-www-form-urlencoded', $form->getEncType());
709
    }
710
711
    public function testAddExtraClass()
712
    {
713
        $form = $this->getStubForm();
714
        $form->addExtraClass('class1');
715
        $form->addExtraClass('class2');
716
        $this->assertStringEndsWith('class1 class2', $form->extraClass());
717
    }
718
719
    public function testRemoveExtraClass()
720
    {
721
        $form = $this->getStubForm();
722
        $form->addExtraClass('class1');
723
        $form->addExtraClass('class2');
724
        $this->assertStringEndsWith('class1 class2', $form->extraClass());
725
        $form->removeExtraClass('class1');
726
        $this->assertStringEndsWith('class2', $form->extraClass());
727
    }
728
729
    public function testAddManyExtraClasses()
730
    {
731
        $form = $this->getStubForm();
732
        //test we can split by a range of spaces and tabs
733
        $form->addExtraClass('class1 class2     class3	class4		class5');
734
        $this->assertStringEndsWith(
735
            'class1 class2 class3 class4 class5',
736
            $form->extraClass()
737
        );
738
        //test that duplicate classes don't get added
739
        $form->addExtraClass('class1 class2');
740
        $this->assertStringEndsWith(
741
            'class1 class2 class3 class4 class5',
742
            $form->extraClass()
743
        );
744
    }
745
746
    public function testRemoveManyExtraClasses()
747
    {
748
        $form = $this->getStubForm();
749
        $form->addExtraClass('class1 class2     class3	class4		class5');
750
        //test we can remove a single class we just added
751
        $form->removeExtraClass('class3');
752
        $this->assertStringEndsWith(
753
            'class1 class2 class4 class5',
754
            $form->extraClass()
755
        );
756
        //check we can remove many classes at once
757
        $form->removeExtraClass('class1 class5');
758
        $this->assertStringEndsWith(
759
            'class2 class4',
760
            $form->extraClass()
761
        );
762
        //check that removing a dud class is fine
763
        $form->removeExtraClass('dudClass');
764
        $this->assertStringEndsWith(
765
            'class2 class4',
766
            $form->extraClass()
767
        );
768
    }
769
770
    public function testDefaultClasses()
771
    {
772
        Form::config()->update(
773
            'default_classes',
774
            array(
775
            'class1',
776
            )
777
        );
778
779
        $form = $this->getStubForm();
780
781
        $this->assertContains('class1', $form->extraClass(), 'Class list does not contain expected class');
782
783
        Form::config()->update(
784
            'default_classes',
785
            array(
786
            'class1',
787
            'class2',
788
            )
789
        );
790
791
        $form = $this->getStubForm();
792
793
        $this->assertContains('class1 class2', $form->extraClass(), 'Class list does not contain expected class');
794
795
        Form::config()->update(
796
            'default_classes',
797
            array(
798
            'class3',
799
            )
800
        );
801
802
        $form = $this->getStubForm();
803
804
        $this->assertContains('class3', $form->extraClass(), 'Class list does not contain expected class');
805
806
        $form->removeExtraClass('class3');
807
808
        $this->assertNotContains('class3', $form->extraClass(), 'Class list contains unexpected class');
809
    }
810
811
    public function testAttributes()
812
    {
813
        $form = $this->getStubForm();
814
        $form->setAttribute('foo', 'bar');
815
        $this->assertEquals('bar', $form->getAttribute('foo'));
816
        $attrs = $form->getAttributes();
817
        $this->assertArrayHasKey('foo', $attrs);
818
        $this->assertEquals('bar', $attrs['foo']);
819
    }
820
821
    /**
822
     * @skipUpgrade
823
     */
824
    public function testButtonClicked()
825
    {
826
        $form = $this->getStubForm();
827
        $action = $form->getRequestHandler()->buttonClicked();
828
        $this->assertNull($action);
829
830
        $controller = new FormTest\TestController();
831
        $form = $controller->Form();
832
        $request = new HTTPRequest(
833
            'POST',
834
            'FormTest_Controller/Form',
835
            array(),
836
            array(
837
            'Email' => '[email protected]',
838
            'SomeRequiredField' => 1,
839
            'action_doSubmit' => 1
840
            )
841
        );
842
        $request->setSession(new Session([]));
843
844
        $form->getRequestHandler()->httpSubmission($request);
845
        $button = $form->getRequestHandler()->buttonClicked();
846
        $this->assertInstanceOf(FormAction::class, $button);
847
        $this->assertEquals('doSubmit', $button->actionName());
848
        $form = new Form(
849
            $controller,
850
            'Form',
851
            new FieldList(new FormAction('doSubmit', 'Inline action')),
852
            new FieldList()
853
        );
854
        $form->disableSecurityToken();
855
        $request = new HTTPRequest(
856
            'POST',
857
            'FormTest_Controller/Form',
858
            array(),
859
            array(
860
            'action_doSubmit' => 1
861
            )
862
        );
863
        $request->setSession(new Session([]));
864
865
        $form->getRequestHandler()->httpSubmission($request);
866
        $button = $form->getRequestHandler()->buttonClicked();
867
        $this->assertInstanceOf(FormAction::class, $button);
868
        $this->assertEquals('doSubmit', $button->actionName());
869
    }
870
871
    public function testCheckAccessAction()
872
    {
873
        $controller = new FormTest\TestController();
874
        $form = new Form(
875
            $controller,
876
            'Form',
877
            new FieldList(),
878
            new FieldList(new FormAction('actionName', 'Action'))
879
        );
880
        $this->assertTrue($form->getRequestHandler()->checkAccessAction('actionName'));
881
882
        $form = new Form(
883
            $controller,
884
            'Form',
885
            new FieldList(new FormAction('inlineAction', 'Inline action')),
886
            new FieldList()
887
        );
888
        $this->assertTrue($form->getRequestHandler()->checkAccessAction('inlineAction'));
889
    }
890
891
    public function testAttributesHTML()
892
    {
893
        $form = $this->getStubForm();
894
895
        $form->setAttribute('foo', 'bar');
896
        $this->assertContains('foo="bar"', $form->getAttributesHTML());
897
898
        $form->setAttribute('foo', null);
899
        $this->assertNotContains('foo="bar"', $form->getAttributesHTML());
900
901
        $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

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