Completed
Push — master ( f39c4d...b2e354 )
by Sam
03:35 queued 03:17
created

FormTest::setUp()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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