Completed
Push — 3.2 ( 17eb35...214c3d )
by Daniel
16:00 queued 07:16
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
/**
4
 * @package framework
5
 * @subpackage tests
6
 */
7
class FormTest extends FunctionalTest {
8
9
	protected static $fixture_file = 'FormTest.yml';
10
11
	protected $extraDataObjects = array(
12
		'FormTest_Player',
13
		'FormTest_Team',
14
	);
15
16
	public function setUp() {
17
		parent::setUp();
18
19
		Config::inst()->update('Director', 'rules', array(
20
			'FormTest_Controller' => 'FormTest_Controller'
21
		));
22
	}
23
24
	public function testLoadDataFromRequest() {
25
		$form = new Form(
26
			new Controller(),
27
			'Form',
28
			new FieldList(
29
				new TextField('key1'),
30
				new TextField('namespace[key2]'),
31
				new TextField('namespace[key3][key4]'),
32
				new TextField('othernamespace[key5][key6][key7]')
33
			),
34
			new FieldList()
35
		);
36
37
		// 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...
38
		$requestData = array(
39
			'key1' => 'val1',
40
			'namespace' => array(
41
				'key2' => 'val2',
42
				'key3' => array(
43
					'key4' => 'val4',
44
				)
45
			),
46
			'othernamespace' => array(
47
				'key5' => array(
48
					'key6' =>array(
49
						'key7' => 'val7'
50
					)
51
				)
52
			)
53
		);
54
55
		$form->loadDataFrom($requestData);
56
57
		$fields = $form->Fields();
58
		$this->assertEquals($fields->fieldByName('key1')->Value(), 'val1');
59
		$this->assertEquals($fields->fieldByName('namespace[key2]')->Value(), 'val2');
60
		$this->assertEquals($fields->fieldByName('namespace[key3][key4]')->Value(), 'val4');
61
		$this->assertEquals($fields->fieldByName('othernamespace[key5][key6][key7]')->Value(), 'val7');
62
	}
63
64
	public function testSubmitReadonlyFields() {
65
		$this->get('FormTest_Controller');
66
67
		// Submitting a value for a readonly field should be ignored
68
		$response = $this->post(
69
			'FormTest_Controller/Form',
70
			array(
71
				'Email' => 'invalid',
72
				'Number' => '888',
73
				'ReadonlyField' => '<script>alert("hacxzored")</script>'
74
				// leaving out "Required" field
75
			)
76
		);
77
78
		// Number field updates its value
79
		$this->assertContains('<input type="text" name="Number" value="888"', $response->getBody());
80
81
82
		// Readonly field remains
83
		$this->assertContains(
84
			'<input type="text" name="ReadonlyField" value="This value is readonly"',
85
			$response->getBody()
86
		);
87
88
		$this->assertNotContains('hacxzored', $response->getBody());
89
	}
90
91
	public function testLoadDataFromUnchangedHandling() {
92
		$form = new Form(
93
			new Controller(),
94
			'Form',
95
			new FieldList(
96
				new TextField('key1'),
97
				new TextField('key2')
98
			),
99
			new FieldList()
100
		);
101
		$form->loadDataFrom(array(
102
			'key1' => 'save',
103
			'key2' => 'dontsave',
104
			'key2_unchanged' => '1'
105
		));
106
		$this->assertEquals(
107
			$form->getData(),
108
			array(
109
				'key1' => 'save',
110
				'key2' => null,
111
			),
112
			'loadDataFrom() doesnt save a field if a matching "<fieldname>_unchanged" flag is set'
113
		);
114
	}
115
116
	public function testLoadDataFromObject() {
117
		$form = new Form(
118
		new Controller(),
119
			'Form',
120
			new FieldList(
121
				new HeaderField('MyPlayerHeader','My Player'),
122
				new TextField('Name'), // appears in both Player and Team
123
				new TextareaField('Biography'),
124
				new DateField('Birthday'),
125
				new NumericField('BirthdayYear') // dynamic property
126
			),
127
			new FieldList()
128
		);
129
130
		$captainWithDetails = $this->objFromFixture('FormTest_Player', 'captainWithDetails');
131
		$form->loadDataFrom($captainWithDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture('F..., 'captainWithDetails') on line 130 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...
132
		$this->assertEquals(
133
			$form->getData(),
134
			array(
135
				'Name' => 'Captain Details',
136
				'Biography' => 'Bio 1',
137
				'Birthday' => '1982-01-01',
138
				'BirthdayYear' => '1982',
139
			),
140
			'LoadDataFrom() loads simple fields and dynamic getters'
141
		);
142
143
		$captainNoDetails = $this->objFromFixture('FormTest_Player', 'captainNoDetails');
144
		$form->loadDataFrom($captainNoDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainNoDetails defined by $this->objFromFixture('F...r', 'captainNoDetails') on line 143 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...
145
		$this->assertEquals(
146
			$form->getData(),
147
			array(
148
				'Name' => 'Captain No Details',
149
				'Biography' => null,
150
				'Birthday' => null,
151
				'BirthdayYear' => 0,
152
			),
153
			'LoadNonBlankDataFrom() loads only fields with values, and doesnt overwrite existing values'
154
		);
155
	}
156
157
	public function testLoadDataFromClearMissingFields() {
158
		$form = new Form(
159
			new Controller(),
160
			'Form',
161
			new FieldList(
162
				new HeaderField('MyPlayerHeader','My Player'),
163
				new TextField('Name'), // appears in both Player and Team
164
				new TextareaField('Biography'),
165
				new DateField('Birthday'),
166
				new NumericField('BirthdayYear'), // dynamic property
167
				$unrelatedField = new TextField('UnrelatedFormField')
168
				//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...
169
			),
170
			new FieldList()
171
		);
172
		$unrelatedField->setValue("random value");
173
174
		$captainWithDetails = $this->objFromFixture('FormTest_Player', 'captainWithDetails');
175
		$captainNoDetails = $this->objFromFixture('FormTest_Player', 'captainNoDetails');
176
		$form->loadDataFrom($captainWithDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture('F..., 'captainWithDetails') on line 174 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...
177
		$this->assertEquals(
178
			$form->getData(),
179
			array(
180
				'Name' => 'Captain Details',
181
				'Biography' => 'Bio 1',
182
				'Birthday' => '1982-01-01',
183
				'BirthdayYear' => '1982',
184
				'UnrelatedFormField' => 'random value',
185
			),
186
			'LoadDataFrom() doesnt overwrite fields not found in the object'
187
		);
188
189
		$captainWithDetails = $this->objFromFixture('FormTest_Player', 'captainNoDetails');
190
		$team2 = $this->objFromFixture('FormTest_Team', 'team2');
191
		$form->loadDataFrom($captainWithDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture('F...r', 'captainNoDetails') on line 189 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...
192
		$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 190 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...
193
		$this->assertEquals(
194
			$form->getData(),
195
			array(
196
				'Name' => 'Team 2',
197
				'Biography' => '',
198
				'Birthday' => '',
199
				'BirthdayYear' => 0,
200
				'UnrelatedFormField' => null,
201
			),
202
			'LoadDataFrom() overwrites fields not found in the object with $clearMissingFields=true'
203
		);
204
	}
205
206
	public function testLoadDataFromIgnoreFalseish() {
207
		$form = new Form(
208
			new Controller(),
209
			'Form',
210
			new FieldList(
211
				new TextField('Biography', 'Biography', 'Custom Default')
212
			),
213
			new FieldList()
214
		);
215
216
		$captainNoDetails = $this->objFromFixture('FormTest_Player', 'captainNoDetails');
217
		$captainWithDetails = $this->objFromFixture('FormTest_Player', 'captainWithDetails');
218
219
		$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 216 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...
220
		$this->assertEquals(
221
			$form->getData(),
222
			array('Biography' => 'Custom Default'),
223
			'LoadDataFrom() doesn\'t overwrite fields when MERGE_IGNORE_FALSEISH set and values are false-ish'
224
		);
225
226
		$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 217 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...
227
		$this->assertEquals(
228
			$form->getData(),
229
			array('Biography' => 'Bio 1'),
230
			'LoadDataFrom() does overwrite fields when MERGE_IGNORE_FALSEISH set and values arent false-ish'
231
		);
232
	}
233
234
	public function testFormMethodOverride() {
235
		$form = $this->getStubForm();
236
		$form->setFormMethod('GET');
237
		$this->assertNull($form->Fields()->dataFieldByName('_method'));
238
239
		$form = $this->getStubForm();
240
		$form->setFormMethod('PUT');
241
		$this->assertEquals($form->Fields()->dataFieldByName('_method')->Value(), 'PUT',
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
		$form = $this->getStubForm();
249
		$form->setFormMethod('DELETE');
250
		$this->assertEquals($form->Fields()->dataFieldByName('_method')->Value(), 'DELETE',
251
			'PUT override in forms has PUT in hiddenfield'
252
		);
253
		$this->assertEquals($form->FormMethod(), 'POST',
254
			'PUT override in forms has POST in <form> tag'
255
		);
256
	}
257
258
	public function testSessionValidationMessage() {
259
		$this->get('FormTest_Controller');
260
261
		$response = $this->post(
262
			'FormTest_Controller/Form',
263
			array(
264
				'Email' => 'invalid',
265
				'Number' => '<a href="http://mysite.com">link</a>' // XSS attempt
266
				// leaving out "Required" field
267
			)
268
		);
269
270
		$this->assertPartialMatchBySelector(
271
			'#Form_Form_Email_Holder span.message',
272
			array(
273
				'Please enter an email address'
274
			),
275
			'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...
276
		);
277
		$this->assertPartialMatchBySelector(
278
			'#Form_Form_SomeRequiredField_Holder span.required',
279
			array(
280
				'"Some Required Field" is required'
281
			),
282
			'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...
283
		);
284
285
		$this->assertContains(
286
			'&#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',
287
			$response->getBody(),
288
			"Validation messages are safely XML encoded"
289
		);
290
		$this->assertNotContains(
291
			'<a href="http://mysite.com">link</a>',
292
			$response->getBody(),
293
			"Unsafe content is not emitted directly inside the response body"
294
		);
295
	}
296
297
	public function testSessionSuccessMessage() {
298
		$this->get('FormTest_Controller');
299
300
		$response = $this->post(
301
			'FormTest_Controller/Form',
302
			array(
303
				'Email' => '[email protected]',
304
				'SomeRequiredField' => 'test',
305
			)
306
		);
307
		$this->assertPartialMatchBySelector(
308
			'#Form_Form_error',
309
			array(
310
				'Test save was successful'
311
			),
312
			'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...
313
		);
314
	}
315
316
	public function testGloballyDisabledSecurityTokenInheritsToNewForm() {
317
		SecurityToken::enable();
318
319
		$form1 = $this->getStubForm();
320
		$this->assertInstanceOf('SecurityToken', $form1->getSecurityToken());
321
322
		SecurityToken::disable();
323
324
		$form2 = $this->getStubForm();
325
		$this->assertInstanceOf('NullSecurityToken', $form2->getSecurityToken());
326
327
		SecurityToken::enable();
328
	}
329
330
	public function testDisableSecurityTokenDoesntAddTokenFormField() {
331
		SecurityToken::enable();
332
333
		$formWithToken = $this->getStubForm();
334
		$this->assertInstanceOf(
335
			'HiddenField',
336
			$formWithToken->Fields()->fieldByName(SecurityToken::get_default_name()),
337
			'Token field added by default'
338
		);
339
340
		$formWithoutToken = $this->getStubForm();
341
		$formWithoutToken->disableSecurityToken();
342
		$this->assertNull(
343
			$formWithoutToken->Fields()->fieldByName(SecurityToken::get_default_name()),
344
			'Token field not added if disableSecurityToken() is set'
345
		);
346
	}
347
348
	public function testDisableSecurityTokenAcceptsSubmissionWithoutToken() {
349
		SecurityToken::enable();
350
		$expectedToken = SecurityToken::inst()->getValue();
351
352
		$response = $this->get('FormTest_ControllerWithSecurityToken');
353
		// can't use submitForm() as it'll automatically insert SecurityID into the POST data
354
		$response = $this->post(
355
			'FormTest_ControllerWithSecurityToken/Form',
356
			array(
357
				'Email' => '[email protected]',
358
				'action_doSubmit' => 1
359
				// leaving out security token
360
			)
361
		);
362
		$this->assertEquals(400, $response->getStatusCode(), 'Submission fails without security token');
363
364
		// Generate a new token which doesn't match the current one
365
		$generator = new RandomGenerator();
366
		$invalidToken = $generator->randomToken('sha1');
367
		$this->assertNotEquals($invalidToken, $expectedToken);
368
369
		// Test token with request
370
		$response = $this->get('FormTest_ControllerWithSecurityToken');
371
		$response = $this->post(
372
			'FormTest_ControllerWithSecurityToken/Form',
373
			array(
374
				'Email' => '[email protected]',
375
				'action_doSubmit' => 1,
376
				'SecurityID' => $invalidToken
377
			)
378
		);
379
		$this->assertEquals(200, $response->getStatusCode(), 'Submission reloads form if security token invalid');
380
		$this->assertTrue(
381
			stripos($response->getBody(), 'name="SecurityID" value="'.$expectedToken.'"') !== false,
382
			'Submission reloads with correct security token after failure'
383
		);
384
		$this->assertTrue(
385
			stripos($response->getBody(), 'name="SecurityID" value="'.$invalidToken.'"') === false,
386
			'Submission reloads without incorrect security token after failure'
387
		);
388
389
		$matched = $this->cssParser()->getBySelector('#Form_Form_Email');
390
		$attrs = $matched[0]->attributes();
391
		$this->assertEquals('[email protected]', (string)$attrs['value'], 'Submitted data is preserved');
392
393
		$response = $this->get('FormTest_ControllerWithSecurityToken');
394
		$tokenEls = $this->cssParser()->getBySelector('#Form_Form_SecurityID');
395
		$this->assertEquals(
396
			1,
397
			count($tokenEls),
398
			'Token form field added for controller without disableSecurityToken()'
399
		);
400
		$token = (string)$tokenEls[0];
401
		$response = $this->submitForm(
402
			'Form_Form',
403
			null,
404
			array(
405
				'Email' => '[email protected]',
406
				'SecurityID' => $token
407
			)
408
		);
409
		$this->assertEquals(200, $response->getStatusCode(), 'Submission suceeds with security token');
410
	}
411
412
	public function testStrictFormMethodChecking() {
413
		$response = $this->get('FormTest_ControllerWithStrictPostCheck');
414
		$response = $this->get(
415
			'FormTest_ControllerWithStrictPostCheck/Form/[email protected]&action_doSubmit=1'
416
		);
417
		$this->assertEquals(405, $response->getStatusCode(), 'Submission fails with wrong method');
418
419
		$response = $this->get('FormTest_ControllerWithStrictPostCheck');
420
		$response = $this->post(
421
			'FormTest_ControllerWithStrictPostCheck/Form',
422
			array(
423
				'Email' => '[email protected]',
424
				'action_doSubmit' => 1
425
			)
426
		);
427
		$this->assertEquals(200, $response->getStatusCode(), 'Submission succeeds with correct method');
428
	}
429
430
	public function testEnableSecurityToken() {
431
		SecurityToken::disable();
432
		$form = $this->getStubForm();
433
		$this->assertFalse($form->getSecurityToken()->isEnabled());
434
		$form->enableSecurityToken();
435
		$this->assertTrue($form->getSecurityToken()->isEnabled());
436
437
		SecurityToken::disable(); // restore original
438
	}
439
440
	public function testDisableSecurityToken() {
441
		SecurityToken::enable();
442
		$form = $this->getStubForm();
443
		$this->assertTrue($form->getSecurityToken()->isEnabled());
444
		$form->disableSecurityToken();
445
		$this->assertFalse($form->getSecurityToken()->isEnabled());
446
447
		SecurityToken::disable(); // restore original
448
	}
449
450
	public function testEncType() {
451
		$form = $this->getStubForm();
452
		$this->assertEquals('application/x-www-form-urlencoded', $form->getEncType());
453
454
		$form->setEncType(Form::ENC_TYPE_MULTIPART);
455
		$this->assertEquals('multipart/form-data', $form->getEncType());
456
457
		$form = $this->getStubForm();
458
		$form->Fields()->push(new FileField(null));
459
		$this->assertEquals('multipart/form-data', $form->getEncType());
460
461
		$form->setEncType(Form::ENC_TYPE_URLENCODED);
462
		$this->assertEquals('application/x-www-form-urlencoded', $form->getEncType());
463
	}
464
465
	public function testAddExtraClass() {
466
		$form = $this->getStubForm();
467
		$form->addExtraClass('class1');
468
		$form->addExtraClass('class2');
469
		$this->assertStringEndsWith('class1 class2', $form->extraClass());
470
	}
471
472
	public function testRemoveExtraClass() {
473
		$form = $this->getStubForm();
474
		$form->addExtraClass('class1');
475
		$form->addExtraClass('class2');
476
		$this->assertStringEndsWith('class1 class2', $form->extraClass());
477
		$form->removeExtraClass('class1');
478
		$this->assertStringEndsWith('class2', $form->extraClass());
479
	}
480
481
	public function testAddManyExtraClasses() {
482
		$form = $this->getStubForm();
483
		//test we can split by a range of spaces and tabs
484
		$form->addExtraClass('class1 class2     class3	class4		class5');
485
		$this->assertStringEndsWith(
486
			'class1 class2 class3 class4 class5',
487
			$form->extraClass()
488
		);
489
		//test that duplicate classes don't get added
490
		$form->addExtraClass('class1 class2');
491
		$this->assertStringEndsWith(
492
			'class1 class2 class3 class4 class5',
493
			$form->extraClass()
494
		);
495
	}
496
497
	public function testRemoveManyExtraClasses() {
498
		$form = $this->getStubForm();
499
		$form->addExtraClass('class1 class2     class3	class4		class5');
500
		//test we can remove a single class we just added
501
		$form->removeExtraClass('class3');
502
		$this->assertStringEndsWith(
503
			'class1 class2 class4 class5',
504
			$form->extraClass()
505
		);
506
		//check we can remove many classes at once
507
		$form->removeExtraClass('class1 class5');
508
		$this->assertStringEndsWith(
509
			'class2 class4',
510
			$form->extraClass()
511
		);
512
		//check that removing a dud class is fine
513
		$form->removeExtraClass('dudClass');
514
		$this->assertStringEndsWith(
515
			'class2 class4',
516
			$form->extraClass()
517
		);
518
	}
519
520
	public function testDefaultClasses() {
521
		Config::nest();
522
523
		Config::inst()->update('Form', 'default_classes', array(
524
			'class1',
525
		));
526
527
		$form = $this->getStubForm();
528
529
		$this->assertContains('class1', $form->extraClass(), 'Class list does not contain expected class');
530
531
		Config::inst()->update('Form', 'default_classes', array(
532
			'class1',
533
			'class2',
534
		));
535
536
		$form = $this->getStubForm();
537
538
		$this->assertContains('class1 class2', $form->extraClass(), 'Class list does not contain expected class');
539
540
		Config::inst()->update('Form', 'default_classes', array(
541
			'class3',
542
		));
543
544
		$form = $this->getStubForm();
545
546
		$this->assertContains('class3', $form->extraClass(), 'Class list does not contain expected class');
547
548
		$form->removeExtraClass('class3');
549
550
		$this->assertNotContains('class3', $form->extraClass(), 'Class list contains unexpected class');
551
552
		Config::unnest();
553
	}
554
555
	public function testAttributes() {
556
		$form = $this->getStubForm();
557
		$form->setAttribute('foo', 'bar');
558
		$this->assertEquals('bar', $form->getAttribute('foo'));
559
		$attrs = $form->getAttributes();
560
		$this->assertArrayHasKey('foo', $attrs);
561
		$this->assertEquals('bar', $attrs['foo']);
562
	}
563
564
	public function testAttributesHTML() {
565
		$form = $this->getStubForm();
566
567
		$form->setAttribute('foo', 'bar');
568
		$this->assertContains('foo="bar"', $form->getAttributesHTML());
569
570
		$form->setAttribute('foo', null);
571
		$this->assertNotContains('foo="bar"', $form->getAttributesHTML());
572
573
		$form->setAttribute('foo', true);
574
		$this->assertContains('foo="foo"', $form->getAttributesHTML());
575
576
		$form->setAttribute('one', 1);
577
		$form->setAttribute('two', 2);
578
		$form->setAttribute('three', 3);
579
		$this->assertNotContains('one="1"', $form->getAttributesHTML('one', 'two'));
580
		$this->assertNotContains('two="2"', $form->getAttributesHTML('one', 'two'));
581
		$this->assertContains('three="3"', $form->getAttributesHTML('one', 'two'));
582
	}
583
584
	function testMessageEscapeHtml() {
585
		$form = $this->getStubForm();
586
		$form->Controller()->handleRequest(new SS_HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
0 ignored issues
show
Deprecated Code introduced by
The method Form::Controller() has been deprecated with message: 4.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
587
		$form->sessionMessage('<em>Escaped HTML</em>', 'good', true);
588
		$parser = new CSSContentParser($form->forTemplate());
589
		$messageEls = $parser->getBySelector('.message');
590
		$this->assertContains(
591
			'&lt;em&gt;Escaped HTML&lt;/em&gt;',
592
			$messageEls[0]->asXML()
593
		);
594
595
		$form = $this->getStubForm();
596
		$form->Controller()->handleRequest(new SS_HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
0 ignored issues
show
Deprecated Code introduced by
The method Form::Controller() has been deprecated with message: 4.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
597
		$form->sessionMessage('<em>Unescaped HTML</em>', 'good', false);
598
		$parser = new CSSContentParser($form->forTemplate());
599
		$messageEls = $parser->getBySelector('.message');
600
		$this->assertContains(
601
			'<em>Unescaped HTML</em>',
602
			$messageEls[0]->asXML()
603
		);
604
	}
605
606
	function testFieldMessageEscapeHtml() {
607
		$form = $this->getStubForm();
608
		$form->Controller()->handleRequest(new SS_HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
0 ignored issues
show
Deprecated Code introduced by
The method Form::Controller() has been deprecated with message: 4.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
609
		$form->addErrorMessage('key1', '<em>Escaped HTML</em>', 'good', true);
610
		$form->setupFormErrors();
611
		$parser = new CSSContentParser($result = $form->forTemplate());
612
		$messageEls = $parser->getBySelector('#Form_Form_key1_Holder .message');
613
		$this->assertContains(
614
			'&lt;em&gt;Escaped HTML&lt;/em&gt;',
615
			$messageEls[0]->asXML()
616
		);
617
618
		$form = $this->getStubForm();
619
		$form->Controller()->handleRequest(new SS_HTTPRequest('GET', '/'), DataModel::inst()); // stub out request
0 ignored issues
show
Deprecated Code introduced by
The method Form::Controller() has been deprecated with message: 4.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
620
		$form->addErrorMessage('key1', '<em>Unescaped HTML</em>', 'good', false);
621
		$form->setupFormErrors();
622
		$parser = new CSSContentParser($form->forTemplate());
623
		$messageEls = $parser->getBySelector('#Form_Form_key1_Holder .message');
624
		$this->assertContains(
625
			'<em>Unescaped HTML</em>',
626
			$messageEls[0]->asXML()
627
		);
628
	}
629
630
    public function testGetExtraFields()
631
    {
632
        $form = new FormTest_ExtraFieldsForm(
633
            new FormTest_Controller(),
634
            'Form',
635
            new FieldList(new TextField('key1')),
636
            new FieldList()
637
        );
638
639
        $data = array(
640
            'key1' => 'test',
641
            'ExtraFieldCheckbox' => false,
642
        );
643
644
        $form->loadDataFrom($data);
645
646
        $formData = $form->getData();
647
        $this->assertEmpty($formData['ExtraFieldCheckbox']);
648
    }
649
650
	protected function getStubForm() {
651
		return new Form(
652
			new FormTest_Controller(),
653
			'Form',
654
			new FieldList(new TextField('key1')),
655
			new FieldList()
656
		);
657
	}
658
659
}
660
661
/**
662
 * @package framework
663
 * @subpackage tests
664
 */
665
class FormTest_Player extends DataObject implements TestOnly {
666
	private static $db = array(
667
		'Name' => 'Varchar',
668
		'Biography' => 'Text',
669
		'Birthday' => 'Date'
670
	);
671
672
	private static $belongs_many_many = array(
673
		'Teams' => 'FormTest_Team'
674
	);
675
676
	private static $has_one = array(
677
		'FavouriteTeam' => 'FormTest_Team',
678
	);
679
680
	public function getBirthdayYear() {
681
		return ($this->Birthday) ? date('Y', strtotime($this->Birthday)) : null;
682
	}
683
684
}
685
686
/**
687
 * @package framework
688
 * @subpackage tests
689
 */
690
class FormTest_Team extends DataObject implements TestOnly {
691
	private static $db = array(
692
		'Name' => 'Varchar',
693
		'Region' => 'Varchar',
694
	);
695
696
	private static $many_many = array(
697
		'Players' => 'FormTest_Player'
698
	);
699
}
700
701
/**
702
 * @package framework
703
 * @subpackage tests
704
 */
705
class FormTest_Controller extends Controller implements TestOnly {
706
707
	private static $allowed_actions = array('Form');
708
709
	private static $url_handlers = array(
710
		'$Action//$ID/$OtherID' => "handleAction",
711
	);
712
713
	protected $template = 'BlankPage';
714
715
	public function Link($action = null) {
716
		return Controller::join_links('FormTest_Controller', $this->getRequest()->latestParam('Action'),
717
			$this->getRequest()->latestParam('ID'), $action);
718
	}
719
720
	public function Form() {
721
		$form = new Form(
722
			$this,
723
			'Form',
724
			new FieldList(
725
				new EmailField('Email'),
726
				new TextField('SomeRequiredField'),
727
				new CheckboxSetField('Boxes', null, array('1'=>'one','2'=>'two')),
728
				new NumericField('Number'),
729
				TextField::create('ReadonlyField')
730
					->setReadonly(true)
731
					->setValue('This value is readonly')
732
			),
733
			new FieldList(
734
				new FormAction('doSubmit')
735
			),
736
			new RequiredFields(
737
				'Email',
738
				'SomeRequiredField'
739
			)
740
		);
741
		$form->disableSecurityToken(); // Disable CSRF protection for easier form submission handling
742
743
		return $form;
744
	}
745
746
	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...
747
		$form->sessionMessage('Test save was successful', 'good');
748
		return $this->redirectBack();
749
	}
750
751
	public function getViewer($action = null) {
752
		return new SSViewer('BlankPage');
753
	}
754
755
}
756
757
/**
758
 * @package framework
759
 * @subpackage tests
760
 */
761
class FormTest_ControllerWithSecurityToken extends Controller implements TestOnly {
762
763
	private static $allowed_actions = array('Form');
764
765
	private static $url_handlers = array(
766
		'$Action//$ID/$OtherID' => "handleAction",
767
	);
768
769
	protected $template = 'BlankPage';
770
771
	public function Link($action = null) {
772
		return Controller::join_links('FormTest_ControllerWithSecurityToken', $this->getRequest()->latestParam('Action'),
773
			$this->getRequest()->latestParam('ID'), $action);
774
	}
775
776
	public function Form() {
777
		$form = new Form(
778
			$this,
779
			'Form',
780
			new FieldList(
781
				new EmailField('Email')
782
			),
783
			new FieldList(
784
				new FormAction('doSubmit')
785
			)
786
		);
787
788
		return $form;
789
	}
790
791
	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...
792
		$form->sessionMessage('Test save was successful', 'good');
793
		return $this->redirectBack();
794
	}
795
796
}
797
798
class FormTest_ControllerWithStrictPostCheck extends Controller implements TestOnly
799
{
800
801
    private static $allowed_actions = array('Form');
802
803
    protected $template = 'BlankPage';
804
805
    public function Link($action = null)
806
    {
807
        return Controller::join_links(
808
            'FormTest_ControllerWithStrictPostCheck',
809
            $this->request->latestParam('Action'),
810
            $this->request->latestParam('ID'),
811
            $action
812
        );
813
    }
814
815
    public function Form()
816
    {
817
        $form = new Form(
818
            $this,
819
            'Form',
820
            new FieldList(
821
                new EmailField('Email')
822
            ),
823
            new FieldList(
824
                new FormAction('doSubmit')
825
            )
826
        );
827
        $form->setFormMethod('POST');
828
        $form->setStrictFormMethodCheck(true);
829
        $form->disableSecurityToken(); // Disable CSRF protection for easier form submission handling
830
831
        return $form;
832
    }
833
834
    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...
835
    {
836
        $form->sessionMessage('Test save was successful', 'good');
837
        return $this->redirectBack();
838
    }
839
}
840
841
class FormTest_ExtraFieldsForm extends Form implements TestOnly {
842
843
    public function getExtraFields() {
844
        $fields = parent::getExtraFields();
845
846
        $fields->push(new CheckboxField('ExtraFieldCheckbox', 'Extra Field Checkbox', 1));
847
848
        return $fields;
849
    }
850
851
}
852