Completed
Push — 3.1 ( 76ce9f...612a91 )
by Daniel
08:13
created

FormTest::testSubmitReadonlyFields()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 13
nc 1
nop 0
dl 0
loc 26
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package framework
4
 * @subpackage tests
5
 */
6
class FormTest extends FunctionalTest {
7
8
	protected static $fixture_file = 'FormTest.yml';
9
10
	protected $extraDataObjects = array(
11
		'FormTest_Player',
12
		'FormTest_Team',
13
	);
14
15
	public function testLoadDataFromRequest() {
16
		$form = new Form(
17
			new Controller(),
18
			'Form',
19
			new FieldList(
20
				new TextField('key1'),
21
				new TextField('namespace[key2]'),
22
				new TextField('namespace[key3][key4]'),
23
				new TextField('othernamespace[key5][key6][key7]')
24
			),
25
			new FieldList()
26
		);
27
28
		// 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...
29
		$requestData = array(
30
			'key1' => 'val1',
31
			'namespace' => array(
32
				'key2' => 'val2',
33
				'key3' => array(
34
					'key4' => 'val4',
35
				)
36
			),
37
			'othernamespace' => array(
38
				'key5' => array(
39
					'key6' =>array(
40
						'key7' => 'val7'
41
					)
42
				)
43
			)
44
		);
45
46
		$form->loadDataFrom($requestData);
47
48
		$fields = $form->Fields();
49
		$this->assertEquals($fields->fieldByName('key1')->Value(), 'val1');
50
		$this->assertEquals($fields->fieldByName('namespace[key2]')->Value(), 'val2');
51
		$this->assertEquals($fields->fieldByName('namespace[key3][key4]')->Value(), 'val4');
52
		$this->assertEquals($fields->fieldByName('othernamespace[key5][key6][key7]')->Value(), 'val7');
53
	}
54
55
	public function testSubmitReadonlyFields() {
56
		$this->get('FormTest_Controller');
57
58
		// Submitting a value for a readonly field should be ignored
59
		$response = $this->post(
60
			'FormTest_Controller/Form',
61
			array(
62
				'Email' => 'invalid',
63
				'Number' => '888',
64
				'ReadonlyField' => '<script>alert("hacxzored")</script>'
65
				// leaving out "Required" field
66
			)
67
		);
68
69
		// Number field updates its value
70
		$this->assertContains('<input type="text" name="Number" value="888"', $response->getBody());
71
72
73
		// Readonly field remains
74
		$this->assertContains(
75
			'<input type="text" name="ReadonlyField" value="This value is readonly"',
76
			$response->getBody()
77
		);
78
79
		$this->assertNotContains('hacxzored', $response->getBody());
80
	}
81
82
	public function testLoadDataFromUnchangedHandling() {
83
		$form = new Form(
84
			new Controller(),
85
			'Form',
86
			new FieldList(
87
				new TextField('key1'),
88
				new TextField('key2')
89
			),
90
			new FieldList()
91
		);
92
		$form->loadDataFrom(array(
93
			'key1' => 'save',
94
			'key2' => 'dontsave',
95
			'key2_unchanged' => '1'
96
		));
97
		$this->assertEquals(
98
			$form->getData(),
99
			array(
100
				'key1' => 'save',
101
				'key2' => null,
102
			),
103
			'loadDataFrom() doesnt save a field if a matching "<fieldname>_unchanged" flag is set'
104
		);
105
	}
106
107
	public function testLoadDataFromObject() {
108
		$form = new Form(
109
			new Controller(),
110
			'Form',
111
			new FieldList(
112
				new HeaderField('MyPlayerHeader','My Player'),
113
				new TextField('Name'), // appears in both Player and Team
114
				new TextareaField('Biography'),
115
				new DateField('Birthday'),
116
				new NumericField('BirthdayYear') // dynamic property
117
			),
118
			new FieldList()
119
		);
120
121
		$captainWithDetails = $this->objFromFixture('FormTest_Player', 'captainWithDetails');
122
		$form->loadDataFrom($captainWithDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture('F..., 'captainWithDetails') on line 121 can be null; however, 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...
123
		$this->assertEquals(
124
			$form->getData(),
125
			array(
126
				'Name' => 'Captain Details',
127
				'Biography' => 'Bio 1',
128
				'Birthday' => '1982-01-01',
129
				'BirthdayYear' => '1982',
130
			),
131
			'LoadDataFrom() loads simple fields and dynamic getters'
132
		);
133
134
		$captainNoDetails = $this->objFromFixture('FormTest_Player', 'captainNoDetails');
135
		$form->loadDataFrom($captainNoDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainNoDetails defined by $this->objFromFixture('F...r', 'captainNoDetails') on line 134 can be null; however, 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...
136
		$this->assertEquals(
137
			$form->getData(),
138
			array(
139
				'Name' => 'Captain No Details',
140
				'Biography' => null,
141
				'Birthday' => null,
142
				'BirthdayYear' => 0,
143
			),
144
			'LoadNonBlankDataFrom() loads only fields with values, and doesnt overwrite existing values'
145
		);
146
	}
147
148
	public function testLoadDataFromClearMissingFields() {
149
		$form = new Form(
150
			new Controller(),
151
			'Form',
152
			new FieldList(
153
				new HeaderField('MyPlayerHeader','My Player'),
154
				new TextField('Name'), // appears in both Player and Team
155
				new TextareaField('Biography'),
156
				new DateField('Birthday'),
157
				new NumericField('BirthdayYear'), // dynamic property
158
				$unrelatedField = new TextField('UnrelatedFormField')
159
				//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...
160
			),
161
			new FieldList()
162
		);
163
		$unrelatedField->setValue("random value");
164
165
		$captainWithDetails = $this->objFromFixture('FormTest_Player', 'captainWithDetails');
166
		$captainNoDetails = $this->objFromFixture('FormTest_Player', 'captainNoDetails');
167
		$form->loadDataFrom($captainWithDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture('F..., 'captainWithDetails') on line 165 can be null; however, 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...
168
		$this->assertEquals(
169
			$form->getData(),
170
			array(
171
				'Name' => 'Captain Details',
172
				'Biography' => 'Bio 1',
173
				'Birthday' => '1982-01-01',
174
				'BirthdayYear' => '1982',
175
				'UnrelatedFormField' => 'random value',
176
			),
177
			'LoadDataFrom() doesnt overwrite fields not found in the object'
178
		);
179
180
		$captainWithDetails = $this->objFromFixture('FormTest_Player', 'captainNoDetails');
181
		$team2 = $this->objFromFixture('FormTest_Team', 'team2');
182
		$form->loadDataFrom($captainWithDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture('F...r', 'captainNoDetails') on line 180 can be null; however, 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...
183
		$form->loadDataFrom($team2, Form::MERGE_CLEAR_MISSING);
0 ignored issues
show
Bug introduced by
It seems like $team2 defined by $this->objFromFixture('FormTest_Team', 'team2') on line 181 can be null; however, 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...
184
		$this->assertEquals(
185
			$form->getData(),
186
			array(
187
				'Name' => 'Team 2',
188
				'Biography' => '',
189
				'Birthday' => '',
190
				'BirthdayYear' => 0,
191
				'UnrelatedFormField' => null,
192
			),
193
			'LoadDataFrom() overwrites fields not found in the object with $clearMissingFields=true'
194
		);
195
	}
196
197
	public function testLoadDataFromIgnoreFalseish() {
198
		$form = new Form(
199
			new Controller(),
200
			'Form',
201
			new FieldList(
202
				new TextField('Biography', 'Biography', 'Custom Default')
203
			),
204
			new FieldList()
205
		);
206
207
		$captainNoDetails = $this->objFromFixture('FormTest_Player', 'captainNoDetails');
208
		$captainWithDetails = $this->objFromFixture('FormTest_Player', 'captainWithDetails');
209
210
		$form->loadDataFrom($captainNoDetails, Form::MERGE_IGNORE_FALSEISH);
0 ignored issues
show
Bug introduced by
It seems like $captainNoDetails defined by $this->objFromFixture('F...r', 'captainNoDetails') on line 207 can be null; however, 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...
211
		$this->assertEquals(
212
			$form->getData(),
213
			array('Biography' => 'Custom Default'),
214
			'LoadDataFrom() doesn\'t overwrite fields when MERGE_IGNORE_FALSEISH set and values are false-ish'
215
		);
216
217
		$form->loadDataFrom($captainWithDetails, Form::MERGE_IGNORE_FALSEISH);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture('F..., 'captainWithDetails') on line 208 can be null; however, 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...
218
		$this->assertEquals(
219
			$form->getData(),
220
			array('Biography' => 'Bio 1'),
221
			'LoadDataFrom() does overwrite fields when MERGE_IGNORE_FALSEISH set and values arent false-ish'
222
		);
223
	}
224
225
	public function testFormMethodOverride() {
226
		$form = $this->getStubForm();
227
		$form->setFormMethod('GET');
228
		$this->assertNull($form->Fields()->dataFieldByName('_method'));
229
230
		$form = $this->getStubForm();
231
		$form->setFormMethod('PUT');
232
		$this->assertEquals($form->Fields()->dataFieldByName('_method')->Value(), 'put',
233
			'PUT override in forms has PUT in hiddenfield'
234
		);
235
		$this->assertEquals($form->FormMethod(), 'post',
236
			'PUT override in forms has POST in <form> tag'
237
		);
238
239
		$form = $this->getStubForm();
240
		$form->setFormMethod('DELETE');
241
		$this->assertEquals($form->Fields()->dataFieldByName('_method')->Value(), 'delete',
242
			'PUT override in forms has PUT in hiddenfield'
243
		);
244
		$this->assertEquals($form->FormMethod(), 'post',
245
			'PUT override in forms has POST in <form> tag'
246
		);
247
	}
248
249
	public function testSessionValidationMessage() {
250
		$this->get('FormTest_Controller');
251
252
		$response = $this->post(
253
			'FormTest_Controller/Form',
254
			array(
255
				'Email' => 'invalid',
256
				'Number' => '<a href="http://mysite.com">link</a>' // XSS attempt
257
				// leaving out "Required" field
258
			)
259
		);
260
		$this->assertPartialMatchBySelector(
261
			'#Email span.message',
262
			array(
263
				'Please enter an email address'
264
			),
265
			'Formfield validation shows note on field if invalid'
0 ignored issues
show
Unused Code introduced by
The call to FormTest::assertPartialMatchBySelector() has too many arguments starting with 'Formfield validation sh...te on field if invalid'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
266
		);
267
		$this->assertPartialMatchBySelector(
268
			'#SomeRequiredField span.required',
269
			array(
270
				'"Some Required Field" is required'
271
			),
272
			'Required fields show a notification on field when left blank'
0 ignored issues
show
Unused Code introduced by
The call to FormTest::assertPartialMatchBySelector() has too many arguments starting with 'Required fields show a ... field when left blank'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
273
		);
274
275
		$this->assertContains(
276
			'&#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',
277
			$response->getBody(),
278
			"Validation messages are safely XML encoded"
279
		);
280
		$this->assertNotContains(
281
			'<a href="http://mysite.com">link</a>',
282
			$response->getBody(),
283
			"Unsafe content is not emitted directly inside the response body"
284
		);
285
	}
286
287
	public function testSessionSuccessMessage() {
288
		$this->get('FormTest_Controller');
289
290
		$response = $this->post(
291
			'FormTest_Controller/Form',
292
			array(
293
				'Email' => '[email protected]',
294
				'SomeRequiredField' => 'test',
295
			)
296
		);
297
		$this->assertPartialMatchBySelector(
298
			'#Form_Form_error',
299
			array(
300
				'Test save was successful'
301
			),
302
			'Form->sessionMessage() shows up after reloading the form'
0 ignored issues
show
Unused Code introduced by
The call to FormTest::assertPartialMatchBySelector() has too many arguments starting with 'Form->sessionMessage() ...ter reloading the form'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
303
		);
304
	}
305
306
	public function testGloballyDisabledSecurityTokenInheritsToNewForm() {
307
		SecurityToken::enable();
308
309
		$form1 = $this->getStubForm();
310
		$this->assertInstanceOf('SecurityToken', $form1->getSecurityToken());
311
312
		SecurityToken::disable();
313
314
		$form2 = $this->getStubForm();
315
		$this->assertInstanceOf('NullSecurityToken', $form2->getSecurityToken());
316
317
		SecurityToken::enable();
318
	}
319
320
	public function testDisableSecurityTokenDoesntAddTokenFormField() {
321
		SecurityToken::enable();
322
323
		$formWithToken = $this->getStubForm();
324
		$this->assertInstanceOf(
325
			'HiddenField',
326
			$formWithToken->Fields()->fieldByName(SecurityToken::get_default_name()),
327
			'Token field added by default'
328
		);
329
330
		$formWithoutToken = $this->getStubForm();
331
		$formWithoutToken->disableSecurityToken();
332
		$this->assertNull(
333
			$formWithoutToken->Fields()->fieldByName(SecurityToken::get_default_name()),
334
			'Token field not added if disableSecurityToken() is set'
335
		);
336
	}
337
338
	public function testDisableSecurityTokenAcceptsSubmissionWithoutToken() {
339
		SecurityToken::enable();
340
		$expectedToken = SecurityToken::inst()->getValue();
341
342
		$response = $this->get('FormTest_ControllerWithSecurityToken');
343
		// can't use submitForm() as it'll automatically insert SecurityID into the POST data
344
		$response = $this->post(
345
			'FormTest_ControllerWithSecurityToken/Form',
346
			array(
347
				'Email' => '[email protected]',
348
				'action_doSubmit' => 1
349
				// leaving out security token
350
			)
351
		);
352
		$this->assertEquals(400, $response->getStatusCode(), 'Submission fails without security token');
353
354
		// Generate a new token which doesn't match the current one
355
		$generator = new RandomGenerator();
356
		$invalidToken = $generator->randomToken('sha1');
357
		$this->assertNotEquals($invalidToken, $expectedToken);
358
359
		// Test token with request
360
		$response = $this->get('FormTest_ControllerWithSecurityToken');
361
		$response = $this->post(
362
			'FormTest_ControllerWithSecurityToken/Form',
363
			array(
364
				'Email' => '[email protected]',
365
				'action_doSubmit' => 1,
366
				'SecurityID' => $invalidToken
367
			)
368
		);
369
		$this->assertEquals(200, $response->getStatusCode(), 'Submission reloads form if security token invalid');
370
		$this->assertTrue(
371
			stripos($response->getBody(), 'name="SecurityID" value="'.$expectedToken.'"') !== false,
372
			'Submission reloads with correct security token after failure'
373
		);
374
		$this->assertTrue(
375
			stripos($response->getBody(), 'name="SecurityID" value="'.$invalidToken.'"') === false,
376
			'Submission reloads without incorrect security token after failure'
377
		);
378
379
		$matched = $this->cssParser()->getBySelector('#Form_Form_Email');
380
		$attrs = $matched[0]->attributes();
381
		$this->assertEquals('[email protected]', (string)$attrs['value'], 'Submitted data is preserved');
382
383
		$response = $this->get('FormTest_ControllerWithSecurityToken');
384
		$tokenEls = $this->cssParser()->getBySelector('#Form_Form_SecurityID');
385
		$this->assertEquals(
386
			1,
387
			count($tokenEls),
388
			'Token form field added for controller without disableSecurityToken()'
389
		);
390
		$token = (string)$tokenEls[0];
391
		$response = $this->submitForm(
392
			'Form_Form',
393
			null,
394
			array(
395
				'Email' => '[email protected]',
396
				'SecurityID' => $token
397
			)
398
		);
399
		$this->assertEquals(200, $response->getStatusCode(), 'Submission suceeds with security token');
400
	}
401
402
	public function testStrictFormMethodChecking() {
403
		$response = $this->get('FormTest_ControllerWithStrictPostCheck');
404
		$response = $this->get(
405
			'FormTest_ControllerWithStrictPostCheck/Form/[email protected]&action_doSubmit=1'
406
		);
407
		$this->assertEquals(405, $response->getStatusCode(), 'Submission fails with wrong method');
408
409
		$response = $this->get('FormTest_ControllerWithStrictPostCheck');
410
		$response = $this->post(
411
			'FormTest_ControllerWithStrictPostCheck/Form',
412
			array(
413
				'Email' => '[email protected]',
414
				'action_doSubmit' => 1
415
			)
416
		);
417
		$this->assertEquals(200, $response->getStatusCode(), 'Submission succeeds with correct method');
418
	}
419
420
	public function testEnableSecurityToken() {
421
		SecurityToken::disable();
422
		$form = $this->getStubForm();
423
		$this->assertFalse($form->getSecurityToken()->isEnabled());
424
		$form->enableSecurityToken();
425
		$this->assertTrue($form->getSecurityToken()->isEnabled());
426
427
		SecurityToken::disable(); // restore original
428
	}
429
430
	public function testDisableSecurityToken() {
431
		SecurityToken::enable();
432
		$form = $this->getStubForm();
433
		$this->assertTrue($form->getSecurityToken()->isEnabled());
434
		$form->disableSecurityToken();
435
		$this->assertFalse($form->getSecurityToken()->isEnabled());
436
437
		SecurityToken::disable(); // restore original
438
	}
439
440
	public function testEncType() {
441
		$form = $this->getStubForm();
442
		$this->assertEquals('application/x-www-form-urlencoded', $form->getEncType());
443
444
		$form->setEncType(Form::ENC_TYPE_MULTIPART);
445
		$this->assertEquals('multipart/form-data', $form->getEncType());
446
447
		$form = $this->getStubForm();
448
		$form->Fields()->push(new FileField(null));
449
		$this->assertEquals('multipart/form-data', $form->getEncType());
450
451
		$form->setEncType(Form::ENC_TYPE_URLENCODED);
452
		$this->assertEquals('application/x-www-form-urlencoded', $form->getEncType());
453
	}
454
455
	public function testAddExtraClass() {
456
		$form = $this->getStubForm();
457
		$form->addExtraClass('class1');
458
		$form->addExtraClass('class2');
459
		$this->assertStringEndsWith('class1 class2', $form->extraClass());
460
	}
461
462
	public function testRemoveExtraClass() {
463
		$form = $this->getStubForm();
464
		$form->addExtraClass('class1');
465
		$form->addExtraClass('class2');
466
		$this->assertStringEndsWith('class1 class2', $form->extraClass());
467
		$form->removeExtraClass('class1');
468
		$this->assertStringEndsWith('class2', $form->extraClass());
469
	}
470
471
	public function testAddManyExtraClasses() {
472
		$form = $this->getStubForm();
473
		//test we can split by a range of spaces and tabs
474
		$form->addExtraClass('class1 class2     class3	class4		class5');
475
		$this->assertStringEndsWith(
476
			'class1 class2 class3 class4 class5',
477
			$form->extraClass()
478
		);
479
		//test that duplicate classes don't get added
480
		$form->addExtraClass('class1 class2');
481
		$this->assertStringEndsWith(
482
			'class1 class2 class3 class4 class5',
483
			$form->extraClass()
484
		);
485
	}
486
487
	public function testRemoveManyExtraClasses() {
488
		$form = $this->getStubForm();
489
		$form->addExtraClass('class1 class2     class3	class4		class5');
490
		//test we can remove a single class we just added
491
		$form->removeExtraClass('class3');
492
		$this->assertStringEndsWith(
493
			'class1 class2 class4 class5',
494
			$form->extraClass()
495
		);
496
		//check we can remove many classes at once
497
		$form->removeExtraClass('class1 class5');
498
		$this->assertStringEndsWith(
499
			'class2 class4',
500
			$form->extraClass()
501
		);
502
		//check that removing a dud class is fine
503
		$form->removeExtraClass('dudClass');
504
		$this->assertStringEndsWith(
505
			'class2 class4',
506
			$form->extraClass()
507
		);
508
	}
509
510
511
	public function testAttributes() {
512
		$form = $this->getStubForm();
513
		$form->setAttribute('foo', 'bar');
514
		$this->assertEquals('bar', $form->getAttribute('foo'));
515
		$attrs = $form->getAttributes();
516
		$this->assertArrayHasKey('foo', $attrs);
517
		$this->assertEquals('bar', $attrs['foo']);
518
	}
519
520
	public function testAttributesHTML() {
521
		$form = $this->getStubForm();
522
523
		$form->setAttribute('foo', 'bar');
524
		$this->assertContains('foo="bar"', $form->getAttributesHTML());
525
526
		$form->setAttribute('foo', null);
527
		$this->assertNotContains('foo="bar"', $form->getAttributesHTML());
528
529
		$form->setAttribute('foo', true);
530
		$this->assertContains('foo="foo"', $form->getAttributesHTML());
531
532
		$form->setAttribute('one', 1);
533
		$form->setAttribute('two', 2);
534
		$form->setAttribute('three', 3);
535
		$this->assertNotContains('one="1"', $form->getAttributesHTML('one', 'two'));
536
		$this->assertNotContains('two="2"', $form->getAttributesHTML('one', 'two'));
537
		$this->assertContains('three="3"', $form->getAttributesHTML('one', 'two'));
538
	}
539
540
	function testMessageEscapeHtml() {
541
		$form = $this->getStubForm();
542
		$form->Controller()->handleRequest(new SS_HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
543
		$form->sessionMessage('<em>Escaped HTML</em>', 'good', true);
544
		$parser = new CSSContentParser($form->forTemplate());
545
		$messageEls = $parser->getBySelector('.message');
546
		$this->assertContains(
547
			'&lt;em&gt;Escaped HTML&lt;/em&gt;',
548
			$messageEls[0]->asXML()
549
		);
550
551
		$form = $this->getStubForm();
552
		$form->Controller()->handleRequest(new SS_HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
553
		$form->sessionMessage('<em>Unescaped HTML</em>', 'good', false);
554
		$parser = new CSSContentParser($form->forTemplate());
555
		$messageEls = $parser->getBySelector('.message');
556
		$this->assertContains(
557
			'<em>Unescaped HTML</em>',
558
			$messageEls[0]->asXML()
559
		);
560
	}
561
562
	function testFieldMessageEscapeHtml() {
563
		$form = $this->getStubForm();
564
		$form->Controller()->handleRequest(new SS_HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
565
		$form->addErrorMessage('key1', '<em>Escaped HTML</em>', 'good', true);
566
		$form->setupFormErrors();
567
		$parser = new CSSContentParser($form->forTemplate());
568
		$messageEls = $parser->getBySelector('#key1 .message');
569
		$this->assertContains(
570
			'&lt;em&gt;Escaped HTML&lt;/em&gt;',
571
			$messageEls[0]->asXML()
572
		);
573
574
		$form = $this->getStubForm();
575
		$form->Controller()->handleRequest(new SS_HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
576
		$form->addErrorMessage('key1', '<em>Unescaped HTML</em>', 'good', false);
577
		$form->setupFormErrors();
578
		$parser = new CSSContentParser($form->forTemplate());
579
		$messageEls = $parser->getBySelector('#key1 .message');
580
		$this->assertContains(
581
			'<em>Unescaped HTML</em>',
582
			$messageEls[0]->asXML()
583
		);
584
	}
585
586
    public function testGetExtraFields()
587
    {
588
        $form = new FormTest_ExtraFieldsForm(
589
            new FormTest_Controller(),
590
            'Form',
591
            new FieldList(new TextField('key1')),
592
            new FieldList()
593
        );
594
595
        $data = array(
596
            'key1' => 'test',
597
            'ExtraFieldCheckbox' => false,
598
        );
599
600
        $form->loadDataFrom($data);
601
602
        $formData = $form->getData();
603
        $this->assertEmpty($formData['ExtraFieldCheckbox']);
604
    }
605
606
	protected function getStubForm() {
607
		return new Form(
608
			new FormTest_Controller(),
609
			'Form',
610
			new FieldList(new TextField('key1')),
611
			new FieldList()
612
		);
613
	}
614
615
}
616
617
class FormTest_Player extends DataObject implements TestOnly {
618
	private static $db = array(
619
		'Name' => 'Varchar',
620
		'Biography' => 'Text',
621
		'Birthday' => 'Date'
622
	);
623
624
	private static $belongs_many_many = array(
625
		'Teams' => 'FormTest_Team'
626
	);
627
628
	private static $has_one = array(
629
		'FavouriteTeam' => 'FormTest_Team',
630
	);
631
632
	public function getBirthdayYear() {
633
		return ($this->Birthday) ? date('Y', strtotime($this->Birthday)) : null;
634
	}
635
636
}
637
638
class FormTest_Team extends DataObject implements TestOnly {
639
	private static $db = array(
640
		'Name' => 'Varchar',
641
		'Region' => 'Varchar',
642
	);
643
644
	private static $many_many = array(
645
		'Players' => 'FormTest_Player'
646
	);
647
}
648
649
class FormTest_Controller extends Controller implements TestOnly {
650
651
	private static $allowed_actions = array('Form');
652
653
	private static $url_handlers = array(
654
		'$Action//$ID/$OtherID' => "handleAction",
655
	);
656
657
	protected $template = 'BlankPage';
658
659
	public function Link($action = null) {
660
		return Controller::join_links('FormTest_Controller', $this->request->latestParam('Action'),
661
			$this->request->latestParam('ID'), $action);
662
	}
663
664
	public function Form() {
665
		$form = new Form(
666
			$this,
667
			'Form',
668
			new FieldList(
669
				new EmailField('Email'),
670
				new TextField('SomeRequiredField'),
671
				new CheckboxSetField('Boxes', null, array('1'=>'one','2'=>'two')),
672
				new NumericField('Number'),
673
				TextField::create('ReadonlyField')
674
					->setReadonly(true)
675
					->setValue('This value is readonly')
676
			),
677
			new FieldList(
678
				new FormAction('doSubmit')
679
			),
680
			new RequiredFields(
681
				'Email',
682
				'SomeRequiredField'
683
			)
684
		);
685
		$form->disableSecurityToken(); // Disable CSRF protection for easier form submission handling
686
687
		return $form;
688
	}
689
690
	public function doSubmit($data, $form, $request) {
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
691
		$form->sessionMessage('Test save was successful', 'good');
692
		return $this->redirectBack();
693
	}
694
695
	public function getViewer($action = null) {
696
		return new SSViewer('BlankPage');
697
	}
698
699
}
700
701
class FormTest_ControllerWithSecurityToken extends Controller implements TestOnly {
702
703
	private static $allowed_actions = array('Form');
704
705
	private static $url_handlers = array(
706
		'$Action//$ID/$OtherID' => "handleAction",
707
	);
708
709
	protected $template = 'BlankPage';
710
711
	public function Link($action = null) {
712
		return Controller::join_links('FormTest_ControllerWithSecurityToken', $this->request->latestParam('Action'),
713
			$this->request->latestParam('ID'), $action);
714
	}
715
716
	public function Form() {
717
		$form = new Form(
718
			$this,
719
			'Form',
720
			new FieldList(
721
				new EmailField('Email')
722
			),
723
			new FieldList(
724
				new FormAction('doSubmit')
725
			)
726
		);
727
728
		return $form;
729
	}
730
731
	public function doSubmit($data, $form, $request) {
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
732
		$form->sessionMessage('Test save was successful', 'good');
733
		return $this->redirectBack();
734
	}
735
736
}
737
738
class FormTest_ControllerWithStrictPostCheck extends Controller implements TestOnly
739
{
740
741
    private static $allowed_actions = array('Form');
742
743
    protected $template = 'BlankPage';
744
745
    public function Link($action = null)
746
    {
747
        return Controller::join_links(
748
            'FormTest_ControllerWithStrictPostCheck',
749
            $this->request->latestParam('Action'),
750
            $this->request->latestParam('ID'),
751
            $action
752
        );
753
    }
754
755
    public function Form()
756
    {
757
        $form = new Form(
758
            $this,
759
            'Form',
760
            new FieldList(
761
                new EmailField('Email')
762
            ),
763
            new FieldList(
764
                new FormAction('doSubmit')
765
            )
766
        );
767
        $form->setFormMethod('POST');
768
        $form->setStrictFormMethodCheck(true);
769
        $form->disableSecurityToken(); // Disable CSRF protection for easier form submission handling
770
771
        return $form;
772
    }
773
774
    public function doSubmit($data, $form, $request)
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
775
    {
776
        $form->sessionMessage('Test save was successful', 'good');
777
        return $this->redirectBack();
778
    }
779
}
780
781
class FormTest_ExtraFieldsForm extends Form implements TestOnly {
782
783
    public function getExtraFields() {
784
        $fields = parent::getExtraFields();
785
786
        $fields->push(new CheckboxField('ExtraFieldCheckbox', 'Extra Field Checkbox', 1));
787
788
        return $fields;
789
    }
790
791
}
792