Completed
Push — new-committers ( 29cb6f...bcba16 )
by Sam
12:18 queued 33s
created

FormTest_Controller::Form()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 22
rs 9.2
cc 1
eloc 16
nc 1
nop 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
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 testLoadDataFromUnchangedHandling() {
68
		$form = new Form(
69
			new Controller(),
70
			'Form',
71
			new FieldList(
72
				new TextField('key1'),
73
				new TextField('key2')
74
			),
75
			new FieldList()
76
		);
77
		$form->loadDataFrom(array(
78
			'key1' => 'save',
79
			'key2' => 'dontsave',
80
			'key2_unchanged' => '1'
81
		));
82
		$this->assertEquals(
83
			$form->getData(),
84
			array(
85
				'key1' => 'save',
86
				'key2' => null,
87
			),
88
			'loadDataFrom() doesnt save a field if a matching "<fieldname>_unchanged" flag is set'
89
		);
90
	}
91
92
	public function testLoadDataFromObject() {
93
		$form = new Form(
94
		new Controller(),
95
			'Form',
96
			new FieldList(
97
				new HeaderField('MyPlayerHeader','My Player'),
98
				new TextField('Name'), // appears in both Player and Team
99
				new TextareaField('Biography'),
100
				new DateField('Birthday'),
101
				new NumericField('BirthdayYear') // dynamic property
102
			),
103
			new FieldList()
104
		);
105
106
		$captainWithDetails = $this->objFromFixture('FormTest_Player', 'captainWithDetails');
107
		$form->loadDataFrom($captainWithDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture('F..., 'captainWithDetails') on line 106 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...
108
		$this->assertEquals(
109
			$form->getData(),
110
			array(
111
				'Name' => 'Captain Details',
112
				'Biography' => 'Bio 1',
113
				'Birthday' => '1982-01-01',
114
				'BirthdayYear' => '1982',
115
			),
116
			'LoadDataFrom() loads simple fields and dynamic getters'
117
		);
118
119
		$captainNoDetails = $this->objFromFixture('FormTest_Player', 'captainNoDetails');
120
		$form->loadDataFrom($captainNoDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainNoDetails defined by $this->objFromFixture('F...r', 'captainNoDetails') on line 119 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...
121
		$this->assertEquals(
122
			$form->getData(),
123
			array(
124
				'Name' => 'Captain No Details',
125
				'Biography' => null,
126
				'Birthday' => null,
127
				'BirthdayYear' => 0,
128
			),
129
			'LoadNonBlankDataFrom() loads only fields with values, and doesnt overwrite existing values'
130
		);
131
	}
132
133
	public function testLoadDataFromClearMissingFields() {
134
		$form = new Form(
135
			new Controller(),
136
			'Form',
137
			new FieldList(
138
				new HeaderField('MyPlayerHeader','My Player'),
139
				new TextField('Name'), // appears in both Player and Team
140
				new TextareaField('Biography'),
141
				new DateField('Birthday'),
142
				new NumericField('BirthdayYear'), // dynamic property
143
				$unrelatedField = new TextField('UnrelatedFormField')
144
				//new CheckboxSetField('Teams') // relation editing
145
			),
146
			new FieldList()
147
		);
148
		$unrelatedField->setValue("random value");
149
150
		$captainWithDetails = $this->objFromFixture('FormTest_Player', 'captainWithDetails');
151
		$captainNoDetails = $this->objFromFixture('FormTest_Player', 'captainNoDetails');
0 ignored issues
show
Unused Code introduced by
$captainNoDetails is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
152
		$form->loadDataFrom($captainWithDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture('F..., 'captainWithDetails') on line 150 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...
153
		$this->assertEquals(
154
			$form->getData(),
155
			array(
156
				'Name' => 'Captain Details',
157
				'Biography' => 'Bio 1',
158
				'Birthday' => '1982-01-01',
159
				'BirthdayYear' => '1982',
160
				'UnrelatedFormField' => 'random value',
161
			),
162
			'LoadDataFrom() doesnt overwrite fields not found in the object'
163
		);
164
165
		$captainWithDetails = $this->objFromFixture('FormTest_Player', 'captainNoDetails');
166
		$team2 = $this->objFromFixture('FormTest_Team', 'team2');
167
		$form->loadDataFrom($captainWithDetails);
0 ignored issues
show
Bug introduced by
It seems like $captainWithDetails defined by $this->objFromFixture('F...r', 'captainNoDetails') 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
		$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 166 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...
169
		$this->assertEquals(
170
			$form->getData(),
171
			array(
172
				'Name' => 'Team 2',
173
				'Biography' => '',
174
				'Birthday' => '',
175
				'BirthdayYear' => 0,
176
				'UnrelatedFormField' => null,
177
			),
178
			'LoadDataFrom() overwrites fields not found in the object with $clearMissingFields=true'
179
		);
180
	}
181
182
	public function testLookupFieldDisabledSaving() {
183
		$object = new DataObjectTest_Team();
184
		$form = new Form(
185
			new Controller(),
186
			'Form',
187
			new FieldList(
188
				new LookupField('Players', 'Players')
189
			),
190
			new FieldList()
191
		);
192
		$form->loadDataFrom(array(
193
			'Players' => array(
194
				14,
195
				18,
196
				22
197
			),
198
		));
199
		$form->saveInto($object);
200
		$playersIds = $object->Players()->getIDList();
201
202
		$this->assertTrue($form->validate());
203
		$this->assertEquals(
204
			$playersIds,
205
			array(),
206
			'saveInto() should not save into the DataObject for the LookupField'
207
		);
208
	}
209
210
	public function testLoadDataFromIgnoreFalseish() {
211
		$form = new Form(
212
			new Controller(),
213
			'Form',
214
			new FieldList(
215
				new TextField('Biography', 'Biography', 'Custom Default')
216
			),
217
			new FieldList()
218
		);
219
220
		$captainNoDetails = $this->objFromFixture('FormTest_Player', 'captainNoDetails');
221
		$captainWithDetails = $this->objFromFixture('FormTest_Player', 'captainWithDetails');
222
223
		$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 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...
224
		$this->assertEquals(
225
			$form->getData(),
226
			array('Biography' => 'Custom Default'),
227
			'LoadDataFrom() doesn\'t overwrite fields when MERGE_IGNORE_FALSEISH set and values are false-ish'
228
		);
229
230
		$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 221 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...
231
		$this->assertEquals(
232
			$form->getData(),
233
			array('Biography' => 'Bio 1'),
234
			'LoadDataFrom() does overwrite fields when MERGE_IGNORE_FALSEISH set and values arent false-ish'
235
		);
236
	}
237
238
	public function testFormMethodOverride() {
239
		$form = $this->getStubForm();
240
		$form->setFormMethod('GET');
241
		$this->assertNull($form->Fields()->dataFieldByName('_method'));
242
243
		$form = $this->getStubForm();
244
		$form->setFormMethod('PUT');
245
		$this->assertEquals($form->Fields()->dataFieldByName('_method')->Value(), 'PUT',
246
			'PUT override in forms has PUT in hiddenfield'
247
		);
248
		$this->assertEquals($form->FormMethod(), 'POST',
249
			'PUT override in forms has POST in <form> tag'
250
		);
251
252
		$form = $this->getStubForm();
253
		$form->setFormMethod('DELETE');
254
		$this->assertEquals($form->Fields()->dataFieldByName('_method')->Value(), 'DELETE',
255
			'PUT override in forms has PUT in hiddenfield'
256
		);
257
		$this->assertEquals($form->FormMethod(), 'POST',
258
			'PUT override in forms has POST in <form> tag'
259
		);
260
	}
261
262
	public function testSessionValidationMessage() {
263
		$this->get('FormTest_Controller');
264
265
		$response = $this->post(
266
			'FormTest_Controller/Form',
267
			array(
268
				'Email' => 'invalid',
269
				'Number' => '<a href="http://mysite.com">link</a>' // XSS attempt
270
				// leaving out "Required" field
271
			)
272
		);
273
274
		$this->assertPartialMatchBySelector(
275
			'#Form_Form_Email_Holder span.message',
276
			array(
277
				'Please enter an email address'
278
			),
279
			'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...
280
		);
281
		$this->assertPartialMatchBySelector(
282
			'#Form_Form_SomeRequiredField_Holder span.required',
283
			array(
284
				'"Some Required Field" is required'
285
			),
286
			'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...
287
		);
288
289
		$this->assertContains(
290
			'&#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',
291
			$response->getBody(),
292
			"Validation messages are safely XML encoded"
293
		);
294
		$this->assertNotContains(
295
			'<a href="http://mysite.com">link</a>',
296
			$response->getBody(),
297
			"Unsafe content is not emitted directly inside the response body"
298
		);
299
	}
300
301
	public function testSessionSuccessMessage() {
302
		$this->get('FormTest_Controller');
303
304
		$response = $this->post(
0 ignored issues
show
Unused Code introduced by
$response is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
305
			'FormTest_Controller/Form',
306
			array(
307
				'Email' => '[email protected]',
308
				'SomeRequiredField' => 'test',
309
			)
310
		);
311
		$this->assertPartialMatchBySelector(
312
			'#Form_Form_error',
313
			array(
314
				'Test save was successful'
315
			),
316
			'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...
317
		);
318
	}
319
320
	public function testGloballyDisabledSecurityTokenInheritsToNewForm() {
321
		SecurityToken::enable();
322
323
		$form1 = $this->getStubForm();
324
		$this->assertInstanceOf('SecurityToken', $form1->getSecurityToken());
325
326
		SecurityToken::disable();
327
328
		$form2 = $this->getStubForm();
329
		$this->assertInstanceOf('NullSecurityToken', $form2->getSecurityToken());
330
331
		SecurityToken::enable();
332
	}
333
334
	public function testDisableSecurityTokenDoesntAddTokenFormField() {
335
		SecurityToken::enable();
336
337
		$formWithToken = $this->getStubForm();
338
		$this->assertInstanceOf(
339
			'HiddenField',
340
			$formWithToken->Fields()->fieldByName(SecurityToken::get_default_name()),
341
			'Token field added by default'
342
		);
343
344
		$formWithoutToken = $this->getStubForm();
345
		$formWithoutToken->disableSecurityToken();
346
		$this->assertNull(
347
			$formWithoutToken->Fields()->fieldByName(SecurityToken::get_default_name()),
348
			'Token field not added if disableSecurityToken() is set'
349
		);
350
	}
351
352
	public function testDisableSecurityTokenAcceptsSubmissionWithoutToken() {
353
		SecurityToken::enable();
354
		$expectedToken = SecurityToken::inst()->getValue();
355
356
		$response = $this->get('FormTest_ControllerWithSecurityToken');
0 ignored issues
show
Unused Code introduced by
$response is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
357
		// can't use submitForm() as it'll automatically insert SecurityID into the POST data
358
		$response = $this->post(
359
			'FormTest_ControllerWithSecurityToken/Form',
360
			array(
361
				'Email' => '[email protected]',
362
				'action_doSubmit' => 1
363
				// leaving out security token
364
			)
365
		);
366
		$this->assertEquals(400, $response->getStatusCode(), 'Submission fails without security token');
367
368
		// Generate a new token which doesn't match the current one
369
		$generator = new RandomGenerator();
370
		$invalidToken = $generator->randomToken('sha1');
371
		$this->assertNotEquals($invalidToken, $expectedToken);
372
373
		// Test token with request
374
		$response = $this->get('FormTest_ControllerWithSecurityToken');
0 ignored issues
show
Unused Code introduced by
$response is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
375
		$response = $this->post(
376
			'FormTest_ControllerWithSecurityToken/Form',
377
			array(
378
				'Email' => '[email protected]',
379
				'action_doSubmit' => 1,
380
				'SecurityID' => $invalidToken
381
			)
382
		);
383
		$this->assertEquals(200, $response->getStatusCode(), 'Submission reloads form if security token invalid');
384
		$this->assertTrue(
385
			stripos($response->getBody(), 'name="SecurityID" value="'.$expectedToken.'"') !== false,
386
			'Submission reloads with correct security token after failure'
387
		);
388
		$this->assertTrue(
389
			stripos($response->getBody(), 'name="SecurityID" value="'.$invalidToken.'"') === false,
390
			'Submission reloads without incorrect security token after failure'
391
		);
392
393
		$matched = $this->cssParser()->getBySelector('#Form_Form_Email');
394
		$attrs = $matched[0]->attributes();
395
		$this->assertEquals('[email protected]', (string)$attrs['value'], 'Submitted data is preserved');
396
397
		$response = $this->get('FormTest_ControllerWithSecurityToken');
0 ignored issues
show
Unused Code introduced by
$response is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
398
		$tokenEls = $this->cssParser()->getBySelector('#Form_Form_SecurityID');
399
		$this->assertEquals(
400
			1,
401
			count($tokenEls),
402
			'Token form field added for controller without disableSecurityToken()'
403
		);
404
		$token = (string)$tokenEls[0];
405
		$response = $this->submitForm(
406
			'Form_Form',
407
			null,
408
			array(
409
				'Email' => '[email protected]',
410
				'SecurityID' => $token
411
			)
412
		);
413
		$this->assertEquals(200, $response->getStatusCode(), 'Submission suceeds with security token');
414
	}
415
416
	public function testStrictFormMethodChecking() {
417
		$response = $this->get('FormTest_ControllerWithStrictPostCheck');
0 ignored issues
show
Unused Code introduced by
$response is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
418
		$response = $this->get(
419
			'FormTest_ControllerWithStrictPostCheck/Form/[email protected]&action_doSubmit=1'
420
		);
421
		$this->assertEquals(405, $response->getStatusCode(), 'Submission fails with wrong method');
422
423
		$response = $this->get('FormTest_ControllerWithStrictPostCheck');
0 ignored issues
show
Unused Code introduced by
$response is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
424
		$response = $this->post(
425
			'FormTest_ControllerWithStrictPostCheck/Form',
426
			array(
427
				'Email' => '[email protected]',
428
				'action_doSubmit' => 1
429
			)
430
		);
431
		$this->assertEquals(200, $response->getStatusCode(), 'Submission succeeds with correct method');
432
	}
433
434 View Code Duplication
	public function testEnableSecurityToken() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
435
		SecurityToken::disable();
436
		$form = $this->getStubForm();
437
		$this->assertFalse($form->getSecurityToken()->isEnabled());
438
		$form->enableSecurityToken();
439
		$this->assertTrue($form->getSecurityToken()->isEnabled());
440
441
		SecurityToken::disable(); // restore original
442
	}
443
444 View Code Duplication
	public function testDisableSecurityToken() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
445
		SecurityToken::enable();
446
		$form = $this->getStubForm();
447
		$this->assertTrue($form->getSecurityToken()->isEnabled());
448
		$form->disableSecurityToken();
449
		$this->assertFalse($form->getSecurityToken()->isEnabled());
450
451
		SecurityToken::disable(); // restore original
452
	}
453
454
	public function testEncType() {
455
		$form = $this->getStubForm();
456
		$this->assertEquals('application/x-www-form-urlencoded', $form->getEncType());
457
458
		$form->setEncType(Form::ENC_TYPE_MULTIPART);
459
		$this->assertEquals('multipart/form-data', $form->getEncType());
460
461
		$form = $this->getStubForm();
462
		$form->Fields()->push(new FileField(null));
463
		$this->assertEquals('multipart/form-data', $form->getEncType());
464
465
		$form->setEncType(Form::ENC_TYPE_URLENCODED);
466
		$this->assertEquals('application/x-www-form-urlencoded', $form->getEncType());
467
	}
468
469
	public function testAddExtraClass() {
470
		$form = $this->getStubForm();
471
		$form->addExtraClass('class1');
472
		$form->addExtraClass('class2');
473
		$this->assertStringEndsWith('class1 class2', $form->extraClass());
474
	}
475
476 View Code Duplication
	public function testRemoveExtraClass() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
477
		$form = $this->getStubForm();
478
		$form->addExtraClass('class1');
479
		$form->addExtraClass('class2');
480
		$this->assertStringEndsWith('class1 class2', $form->extraClass());
481
		$form->removeExtraClass('class1');
482
		$this->assertStringEndsWith('class2', $form->extraClass());
483
	}
484
485 View Code Duplication
	public function testAddManyExtraClasses() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
486
		$form = $this->getStubForm();
487
		//test we can split by a range of spaces and tabs
488
		$form->addExtraClass('class1 class2     class3	class4		class5');
489
		$this->assertStringEndsWith(
490
			'class1 class2 class3 class4 class5',
491
			$form->extraClass()
492
		);
493
		//test that duplicate classes don't get added
494
		$form->addExtraClass('class1 class2');
495
		$this->assertStringEndsWith(
496
			'class1 class2 class3 class4 class5',
497
			$form->extraClass()
498
		);
499
	}
500
501 View Code Duplication
	public function testRemoveManyExtraClasses() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
502
		$form = $this->getStubForm();
503
		$form->addExtraClass('class1 class2     class3	class4		class5');
504
		//test we can remove a single class we just added
505
		$form->removeExtraClass('class3');
506
		$this->assertStringEndsWith(
507
			'class1 class2 class4 class5',
508
			$form->extraClass()
509
		);
510
		//check we can remove many classes at once
511
		$form->removeExtraClass('class1 class5');
512
		$this->assertStringEndsWith(
513
			'class2 class4',
514
			$form->extraClass()
515
		);
516
		//check that removing a dud class is fine
517
		$form->removeExtraClass('dudClass');
518
		$this->assertStringEndsWith(
519
			'class2 class4',
520
			$form->extraClass()
521
		);
522
	}
523
524
	public function testDefaultClasses() {
525
		Config::nest();
526
527
		Config::inst()->update('Form', 'default_classes', array(
528
			'class1',
529
		));
530
531
		$form = $this->getStubForm();
532
533
		$this->assertContains('class1', $form->extraClass(), 'Class list does not contain expected class');
534
535
		Config::inst()->update('Form', 'default_classes', array(
536
			'class1',
537
			'class2',
538
		));
539
540
		$form = $this->getStubForm();
541
542
		$this->assertContains('class1 class2', $form->extraClass(), 'Class list does not contain expected class');
543
544
		Config::inst()->update('Form', 'default_classes', array(
545
			'class3',
546
		));
547
548
		$form = $this->getStubForm();
549
550
		$this->assertContains('class3', $form->extraClass(), 'Class list does not contain expected class');
551
552
		$form->removeExtraClass('class3');
553
554
		$this->assertNotContains('class3', $form->extraClass(), 'Class list contains unexpected class');
555
556
		Config::unnest();
557
	}
558
559 View Code Duplication
	public function testAttributes() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
560
		$form = $this->getStubForm();
561
		$form->setAttribute('foo', 'bar');
562
		$this->assertEquals('bar', $form->getAttribute('foo'));
563
		$attrs = $form->getAttributes();
564
		$this->assertArrayHasKey('foo', $attrs);
565
		$this->assertEquals('bar', $attrs['foo']);
566
	}
567
568
	public function testAttributesHTML() {
569
		$form = $this->getStubForm();
570
571
		$form->setAttribute('foo', 'bar');
572
		$this->assertContains('foo="bar"', $form->getAttributesHTML());
573
574
		$form->setAttribute('foo', null);
575
		$this->assertNotContains('foo="bar"', $form->getAttributesHTML());
576
577
		$form->setAttribute('foo', true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
578
		$this->assertContains('foo="foo"', $form->getAttributesHTML());
579
580
		$form->setAttribute('one', 1);
581
		$form->setAttribute('two', 2);
582
		$form->setAttribute('three', 3);
583
		$this->assertNotContains('one="1"', $form->getAttributesHTML('one', 'two'));
584
		$this->assertNotContains('two="2"', $form->getAttributesHTML('one', 'two'));
585
		$this->assertContains('three="3"', $form->getAttributesHTML('one', 'two'));
586
	}
587
588
	function testMessageEscapeHtml() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
589
		$form = $this->getStubForm();
590
		$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...
591
		$form->sessionMessage('<em>Escaped HTML</em>', 'good', true);
592
		$parser = new CSSContentParser($form->forTemplate());
593
		$messageEls = $parser->getBySelector('.message');
594
		$this->assertContains(
595
			'&lt;em&gt;Escaped HTML&lt;/em&gt;',
596
			$messageEls[0]->asXML()
597
		);
598
599
		$form = $this->getStubForm();
600
		$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...
601
		$form->sessionMessage('<em>Unescaped HTML</em>', 'good', false);
602
		$parser = new CSSContentParser($form->forTemplate());
603
		$messageEls = $parser->getBySelector('.message');
604
		$this->assertContains(
605
			'<em>Unescaped HTML</em>',
606
			$messageEls[0]->asXML()
607
		);
608
	}
609
610
	function testFieldMessageEscapeHtml() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

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