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