Completed
Push — 3.7 ( 81b2d8...ef0909 )
by
unknown
09:42
created

MemberTest   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 1099
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 21

Importance

Changes 0
Metric Value
dl 0
loc 1099
rs 7.684
c 0
b 0
f 0
wmc 49
lcom 2
cbo 21

44 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A __destruct() 0 3 1
A setUp() 0 7 1
A tearDown() 0 4 1
A testPasswordEncryptionUpdatedOnChangedPassword() 0 15 1
A testWriteDoesntMergeNewRecordWithExistingMember() 0 9 1
A testWriteDoesntMergeExistingMemberOnIdentifierChange() 0 12 1
A testDefaultPasswordEncryptionOnMember() 0 17 1
A testKeepsEncryptionOnEmptyPasswords() 0 16 1
A testSetPassword() 0 7 1
A testPasswordChangeLogging() 0 36 1
A testChangedPasswordEmaling() 0 14 1
A testForgotPasswordEmaling() 0 20 1
B testValidatePassword() 0 78 1
A testPasswordExpirySetting() 0 17 1
A testIsPasswordExpired() 0 22 1
A testMemberWithNoDateFormatFallsbackToGlobalLocaleDefaultFormat() 0 7 1
A testInGroups() 0 24 1
A testRemoveGroups() 0 25 1
A testAddToGroupByCode() 0 23 1
A testRemoveFromGroupByCode() 0 27 1
A testInGroup() 0 52 1
A testCanManipulateOwnRecord() 0 27 1
A testAuthorisedMembersCanManipulateOthersRecords() 0 14 1
A testExtendedCan() 0 38 1
A testName() 0 10 1
A testMembersWithSecurityAdminAccessCantEditAdminsUnlessTheyreAdminsThemselves() 0 17 1
A testOnChangeGroups() 0 35 1
A testAllowedGroupsListbox() 0 26 1
A testOnChangeGroupsByAdd() 0 46 1
A testOnChangeGroupsBySetIDList() 0 16 1
A testUpdateCMSFields() 0 12 1
A testMap_in_groupsReturnsAll() 0 4 1
A testMap_in_groupsReturnsAdmins() 0 13 1
A addExtensions() 0 6 3
A removeExtensions() 0 6 3
A testGenerateAutologinTokenAndStoreHash() 0 11 1
A testValidateAutoLoginToken() 0 16 1
A testCanDelete() 0 27 1
A testFailedLoginCount() 0 23 2
B testMemberValidator() 0 65 1
B testMemberValidatorWithExtensions() 0 57 1
A testCustomMemberValidator() 0 48 1
A testCurrentUser() 0 9 1

How to fix   Complexity   

Complex Class

Complex classes like MemberTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MemberTest, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package framework
4
 * @subpackage tests
5
 */
6
class MemberTest extends FunctionalTest {
7
	protected static $fixture_file = 'MemberTest.yml';
8
9
	protected $orig = array();
10
	protected $local = null;
11
12
	protected $illegalExtensions = array(
13
		'Member' => array(
14
			// TODO Coupling with modules, this should be resolved by automatically
15
			// removing all applied extensions before a unit test
16
			'ForumRole',
17
			'OpenIDAuthenticatedRole'
18
		)
19
	);
20
21
	public function __construct() {
22
		parent::__construct();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class FunctionalTest as the method __construct() does only exist in the following sub-classes of FunctionalTest: MemberTest. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
23
24
		//Setting the locale has to happen in the constructor (using the setUp and tearDown methods doesn't work)
25
		//This is because the test relies on the yaml file being interpreted according to a particular date format
26
		//and this setup occurs before the setUp method is run
27
		$this->local = i18n::default_locale();
0 ignored issues
show
Deprecated Code introduced by
The method i18n::default_locale() has been deprecated with message: since version 4.0; Use the "i18n.default_locale" config setting instead

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...
28
		i18n::set_default_locale('en_US');
0 ignored issues
show
Deprecated Code introduced by
The method i18n::set_default_locale() has been deprecated with message: since version 4.0; Use the "i18n.default_locale" config setting instead

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...
29
	}
30
31
	public function __destruct() {
32
		i18n::set_default_locale($this->local);
0 ignored issues
show
Deprecated Code introduced by
The method i18n::set_default_locale() has been deprecated with message: since version 4.0; Use the "i18n.default_locale" config setting instead

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...
33
	}
34
35
	public function setUp() {
36
		parent::setUp();
37
38
		$this->orig['Member_unique_identifier_field'] = Member::config()->unique_identifier_field;
0 ignored issues
show
Documentation introduced by
The property unique_identifier_field does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
39
		Member::config()->unique_identifier_field = 'Email';
0 ignored issues
show
Documentation introduced by
The property unique_identifier_field does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
40
		Member::set_password_validator(null);
41
	}
42
43
	public function tearDown() {
44
		Member::config()->unique_identifier_field = $this->orig['Member_unique_identifier_field'];
0 ignored issues
show
Documentation introduced by
The property unique_identifier_field does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
45
		parent::tearDown();
46
	}
47
48
	public function testPasswordEncryptionUpdatedOnChangedPassword()
49
	{
50
		Config::inst()->update('Security', 'password_encryption_algorithm', 'none');
51
		$member = Member::create();
52
		$member->SetPassword = 'password';
0 ignored issues
show
Bug introduced by
The property SetPassword does not seem to exist. Did you mean Password?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
53
		$member->write();
54
		$this->assertEquals('password', $member->Password);
55
		$this->assertEquals('none', $member->PasswordEncryption);
56
		Config::inst()->update('Security', 'password_encryption_algorithm', 'blowfish');
57
		$member->SetPassword = 'newpassword';
0 ignored issues
show
Bug introduced by
The property SetPassword does not seem to exist. Did you mean Password?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
58
		$member->write();
59
		$this->assertNotEquals('password', $member->Password);
60
		$this->assertNotEquals('newpassword', $member->Password);
61
		$this->assertEquals('blowfish', $member->PasswordEncryption);
62
	}
63
64
	/**
65
	 * @expectedException ValidationException
66
	 */
67
	public function testWriteDoesntMergeNewRecordWithExistingMember() {
68
		$m1 = new Member();
69
		$m1->Email = '[email protected]';
70
		$m1->write();
71
72
		$m2 = new Member();
73
		$m2->Email = '[email protected]';
74
		$m2->write();
75
	}
76
77
	/**
78
	 * @expectedException ValidationException
79
	 */
80
	public function testWriteDoesntMergeExistingMemberOnIdentifierChange() {
81
		$m1 = new Member();
82
		$m1->Email = '[email protected]';
83
		$m1->write();
84
85
		$m2 = new Member();
86
		$m2->Email = '[email protected]';
87
		$m2->write();
88
89
		$m2->Email = '[email protected]';
90
		$m2->write();
91
	}
92
93
	public function testDefaultPasswordEncryptionOnMember() {
94
		$memberWithPassword = new Member();
95
		$memberWithPassword->Password = 'mypassword';
96
		$memberWithPassword->write();
97
		$this->assertEquals(
98
			$memberWithPassword->PasswordEncryption,
99
			Security::config()->password_encryption_algorithm,
100
			'Password encryption is set for new member records on first write (with setting "Password")'
101
		);
102
103
		$memberNoPassword = new Member();
104
		$memberNoPassword->write();
105
		$this->assertNull(
106
			$memberNoPassword->PasswordEncryption,
107
			'Password encryption is not set for new member records on first write, when not setting a "Password")'
108
		);
109
	}
110
111
	public function testKeepsEncryptionOnEmptyPasswords() {
112
		$member = new Member();
113
		$member->Password = 'mypassword';
114
		$member->PasswordEncryption = 'sha1_v2.4';
115
		$member->write();
116
117
		$member->Password = '';
118
		$member->write();
119
120
		$this->assertEquals(
121
			Security::config()->get('password_encryption_algorithm'),
122
            $member->PasswordEncryption
123
		);
124
		$result = $member->checkPassword('');
125
		$this->assertTrue($result->valid());
126
	}
127
128
	public function testSetPassword() {
129
		$member = $this->objFromFixture('Member', 'test');
130
		$member->Password = "test1";
0 ignored issues
show
Documentation introduced by
The property Password does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
131
		$member->write();
132
		$result = $member->checkPassword('test1');
133
		$this->assertTrue($result->valid());
134
	}
135
136
	/**
137
	 * Test that password changes are logged properly
138
	 */
139
	public function testPasswordChangeLogging() {
140
		$member = $this->objFromFixture('Member', 'test');
141
		$this->assertNotNull($member);
142
		$member->Password = "test1";
0 ignored issues
show
Documentation introduced by
The property Password does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
143
		$member->write();
144
145
		$member->Password = "test2";
0 ignored issues
show
Documentation introduced by
The property Password does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
146
		$member->write();
147
148
		$member->Password = "test3";
0 ignored issues
show
Documentation introduced by
The property Password does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
149
		$member->write();
150
151
		$passwords = DataObject::get("MemberPassword", "\"MemberID\" = $member->ID", "\"Created\" DESC, \"ID\" DESC")
152
			->getIterator();
153
		$this->assertNotNull($passwords);
154
		$passwords->rewind();
155
		$this->assertTrue($passwords->current()->checkPassword('test3'), "Password test3 not found in MemberRecord");
156
157
		$passwords->next();
158
		$this->assertTrue($passwords->current()->checkPassword('test2'), "Password test2 not found in MemberRecord");
159
160
		$passwords->next();
161
		$this->assertTrue($passwords->current()->checkPassword('test1'), "Password test1 not found in MemberRecord");
162
163
		$passwords->next();
164
		$this->assertInstanceOf('DataObject', $passwords->current());
165
		$this->assertTrue($passwords->current()->checkPassword('1nitialPassword'),
166
			"Password 1nitialPassword not found in MemberRecord");
167
168
		//check we don't retain orphaned records when a member is deleted
169
		$member->delete();
170
171
		$passwords = MemberPassword::get()->filter('MemberID', $member->OldID);
172
173
		$this->assertCount(0, $passwords);
174
	}
175
176
	/**
177
	 * Test that changed passwords will send an email
178
	 */
179
	public function testChangedPasswordEmaling() {
180
		Config::inst()->update('Member', 'notify_password_change', true);
181
182
		$this->clearEmails();
183
184
		$member = $this->objFromFixture('Member', 'test');
185
		$this->assertNotNull($member);
186
		$valid = $member->changePassword('32asDF##$$%%');
187
		$this->assertTrue($valid->valid());
188
189
		$this->assertEmailSent('[email protected]', null, 'Your password has been changed',
190
			'/testuser@example\.com/');
191
192
	}
193
194
	/**
195
	 * Test that triggering "forgotPassword" sends an Email with a reset link
196
	 */
197
	public function testForgotPasswordEmaling() {
198
		$this->clearEmails();
199
		$this->autoFollowRedirection = false;
200
201
		$member = $this->objFromFixture('Member', 'test');
202
		$this->assertNotNull($member);
203
204
		// Initiate a password-reset
205
		$response = $this->post('Security/LostPasswordForm', array('Email' => $member->Email));
206
207
		$this->assertEquals($response->getStatusCode(), 302);
208
209
		// We should get redirected to Security/passwordsent
210
		$this->assertContains('Security/passwordsent/[email protected]',
211
			urldecode($response->getHeader('Location')));
212
213
		// Check existance of reset link
214
		$this->assertEmailSent("[email protected]", null, 'Your password reset link',
215
			'/Security\/changepassword\?m='.$member->ID.'&t=[^"]+/');
216
	}
217
218
	/**
219
	 * Test that passwords validate against NZ e-government guidelines
220
	 *  - don't allow the use of the last 6 passwords
221
	 *  - require at least 3 of lowercase, uppercase, digits and punctuation
222
	 *  - at least 7 characters long
223
	 */
224
	public function testValidatePassword() {
225
		$member = $this->objFromFixture('Member', 'test');
226
		$this->assertNotNull($member);
227
228
		Member::set_password_validator(new MemberTest_PasswordValidator());
229
230
		// BAD PASSWORDS
231
232
		$valid = $member->changePassword('shorty');
233
		$this->assertFalse($valid->valid());
234
		$this->assertContains("TOO_SHORT", $valid->codeList());
235
236
		$valid = $member->changePassword('longone');
237
		$this->assertNotContains("TOO_SHORT", $valid->codeList());
238
		$this->assertContains("LOW_CHARACTER_STRENGTH", $valid->codeList());
239
		$this->assertFalse($valid->valid());
240
241
		$valid = $member->changePassword('w1thNumb3rs');
242
		$this->assertNotContains("LOW_CHARACTER_STRENGTH", $valid->codeList());
243
		$this->assertTrue($valid->valid());
244
245
		// Clear out the MemberPassword table to ensure that the system functions properly in that situation
246
		DB::query("DELETE FROM \"MemberPassword\"");
247
248
		// GOOD PASSWORDS
249
250
		$valid = $member->changePassword('withSym###Ls');
251
		$this->assertNotContains("LOW_CHARACTER_STRENGTH", $valid->codeList());
252
		$this->assertTrue($valid->valid());
253
254
		$valid = $member->changePassword('withSym###Ls2');
255
		$this->assertTrue($valid->valid());
256
257
		$valid = $member->changePassword('withSym###Ls3');
258
		$this->assertTrue($valid->valid());
259
260
		$valid = $member->changePassword('withSym###Ls4');
261
		$this->assertTrue($valid->valid());
262
263
		$valid = $member->changePassword('withSym###Ls5');
264
		$this->assertTrue($valid->valid());
265
266
		$valid = $member->changePassword('withSym###Ls6');
267
		$this->assertTrue($valid->valid());
268
269
		$valid = $member->changePassword('withSym###Ls7');
270
		$this->assertTrue($valid->valid());
271
272
		// CAN'T USE PASSWORDS 2-7, but I can use pasword 1
273
274
		$valid = $member->changePassword('withSym###Ls2');
275
		$this->assertFalse($valid->valid());
276
		$this->assertContains("PREVIOUS_PASSWORD", $valid->codeList());
277
278
		$valid = $member->changePassword('withSym###Ls5');
279
		$this->assertFalse($valid->valid());
280
		$this->assertContains("PREVIOUS_PASSWORD", $valid->codeList());
281
282
		$valid = $member->changePassword('withSym###Ls7');
283
		$this->assertFalse($valid->valid());
284
		$this->assertContains("PREVIOUS_PASSWORD", $valid->codeList());
285
286
		$valid = $member->changePassword('withSym###Ls');
287
		$this->assertTrue($valid->valid());
288
289
		// HAVING DONE THAT, PASSWORD 2 is now available from the list
290
291
		$valid = $member->changePassword('withSym###Ls2');
292
		$this->assertTrue($valid->valid());
293
294
		$valid = $member->changePassword('withSym###Ls3');
295
		$this->assertTrue($valid->valid());
296
297
		$valid = $member->changePassword('withSym###Ls4');
298
		$this->assertTrue($valid->valid());
299
300
		Member::set_password_validator(null);
301
	}
302
303
	/**
304
	 * Test that the PasswordExpiry date is set when passwords are changed
305
	 */
306
	public function testPasswordExpirySetting() {
307
		Member::config()->password_expiry_days = 90;
0 ignored issues
show
Documentation introduced by
The property password_expiry_days does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
308
309
		$member = $this->objFromFixture('Member', 'test');
310
		$this->assertNotNull($member);
311
		$valid = $member->changePassword("Xx?1234234");
312
		$this->assertTrue($valid->valid());
313
314
		$expiryDate = date('Y-m-d', time() + 90*86400);
315
		$this->assertEquals($expiryDate, $member->PasswordExpiry);
316
317
		Member::config()->password_expiry_days = null;
0 ignored issues
show
Documentation introduced by
The property password_expiry_days does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
318
		$valid = $member->changePassword("Xx?1234235");
319
		$this->assertTrue($valid->valid());
320
321
		$this->assertNull($member->PasswordExpiry);
322
	}
323
324
	public function testIsPasswordExpired() {
325
		$member = $this->objFromFixture('Member', 'test');
326
		$this->assertNotNull($member);
327
		$this->assertFalse($member->isPasswordExpired());
328
329
		$member = $this->objFromFixture('Member', 'noexpiry');
330
		$member->PasswordExpiry = null;
0 ignored issues
show
Documentation introduced by
The property PasswordExpiry does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
331
		$this->assertFalse($member->isPasswordExpired());
332
333
		$member = $this->objFromFixture('Member', 'expiredpassword');
334
		$this->assertTrue($member->isPasswordExpired());
335
336
		// Check the boundary conditions
337
		// If PasswordExpiry == today, then it's expired
338
		$member->PasswordExpiry = date('Y-m-d');
0 ignored issues
show
Documentation introduced by
The property PasswordExpiry does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
339
		$this->assertTrue($member->isPasswordExpired());
340
341
		// If PasswordExpiry == tomorrow, then it's not
342
		$member->PasswordExpiry = date('Y-m-d', time() + 86400);
0 ignored issues
show
Documentation introduced by
The property PasswordExpiry does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
343
		$this->assertFalse($member->isPasswordExpired());
344
345
	}
346
347
	public function testMemberWithNoDateFormatFallsbackToGlobalLocaleDefaultFormat() {
348
		Config::inst()->update('i18n', 'date_format', 'yyyy-MM-dd');
349
		Config::inst()->update('i18n', 'time_format', 'H:mm');
350
		$member = $this->objFromFixture('Member', 'noformatmember');
351
		$this->assertEquals('yyyy-MM-dd', $member->DateFormat);
352
		$this->assertEquals('H:mm', $member->TimeFormat);
353
	}
354
355
	public function testInGroups() {
356
		$staffmember = $this->objFromFixture('Member', 'staffmember');
357
		$managementmember = $this->objFromFixture('Member', 'managementmember');
358
		$accountingmember = $this->objFromFixture('Member', 'accountingmember');
359
		$ceomember = $this->objFromFixture('Member', 'ceomember');
360
361
		$staffgroup = $this->objFromFixture('Group', 'staffgroup');
362
		$managementgroup = $this->objFromFixture('Group', 'managementgroup');
363
		$accountinggroup = $this->objFromFixture('Group', 'accountinggroup');
364
		$ceogroup = $this->objFromFixture('Group', 'ceogroup');
365
366
		$this->assertTrue(
367
			$staffmember->inGroups(array($staffgroup, $managementgroup)),
368
			'inGroups() succeeds if a membership is detected on one of many passed groups'
369
		);
370
		$this->assertFalse(
371
			$staffmember->inGroups(array($ceogroup, $managementgroup)),
372
			'inGroups() fails if a membership is detected on none of the passed groups'
373
		);
374
		$this->assertFalse(
375
			$ceomember->inGroups(array($staffgroup, $managementgroup), true),
376
			'inGroups() fails if no direct membership is detected on any of the passed groups (in strict mode)'
377
		);
378
	}
379
380
	/**
381
	 * Assertions to check that Member_GroupSet is functionally equivalent to ManyManyList
382
	 */
383
	public function testRemoveGroups()
384
	{
385
		$staffmember = $this->objFromFixture('Member', 'staffmember');
386
387
		$staffgroup = $this->objFromFixture('Group', 'staffgroup');
388
		$managementgroup = $this->objFromFixture('Group', 'managementgroup');
389
390
		$this->assertTrue(
391
			$staffmember->inGroups(array($staffgroup, $managementgroup)),
392
			'inGroups() succeeds if a membership is detected on one of many passed groups'
393
		);
394
395
		$staffmember->Groups()->remove($managementgroup);
396
		$this->assertFalse(
397
			$staffmember->inGroup($managementgroup),
398
			'member was not removed from group using ->Groups()->remove()'
399
		);
400
401
		$staffmember->Groups()->removeAll();
402
		$this->assertEquals(
403
			0,
404
			$staffmember->Groups()->count(),
405
			'member was not removed from all groups using ->Groups()->removeAll()'
406
		);
407
	}
408
409
	public function testAddToGroupByCode() {
410
		$grouplessMember = $this->objFromFixture('Member', 'grouplessmember');
411
		$memberlessGroup = $this->objFromFixture('Group','memberlessgroup');
412
413
		$this->assertFalse($grouplessMember->Groups()->exists());
414
		$this->assertFalse($memberlessGroup->Members()->exists());
415
416
		$grouplessMember->addToGroupByCode('memberless');
417
418
		$this->assertEquals($memberlessGroup->Members()->Count(), 1);
419
		$this->assertEquals($grouplessMember->Groups()->Count(), 1);
420
421
		$grouplessMember->addToGroupByCode('somegroupthatwouldneverexist', 'New Group');
422
		$this->assertEquals($grouplessMember->Groups()->Count(), 2);
423
424
		$group = DataObject::get_one('Group', array(
425
			'"Group"."Code"' => 'somegroupthatwouldneverexist'
426
		));
427
		$this->assertNotNull($group);
428
		$this->assertEquals($group->Code, 'somegroupthatwouldneverexist');
429
		$this->assertEquals($group->Title, 'New Group');
430
431
	}
432
433
	public function testRemoveFromGroupByCode() {
434
		$grouplessMember = $this->objFromFixture('Member', 'grouplessmember');
435
		$memberlessGroup = $this->objFromFixture('Group','memberlessgroup');
436
437
		$this->assertFalse($grouplessMember->Groups()->exists());
438
		$this->assertFalse($memberlessGroup->Members()->exists());
439
440
		$grouplessMember->addToGroupByCode('memberless');
441
442
		$this->assertEquals($memberlessGroup->Members()->Count(), 1);
443
		$this->assertEquals($grouplessMember->Groups()->Count(), 1);
444
445
		$grouplessMember->addToGroupByCode('somegroupthatwouldneverexist', 'New Group');
446
		$this->assertEquals($grouplessMember->Groups()->Count(), 2);
447
448
		$group = DataObject::get_one('Group', "\"Code\" = 'somegroupthatwouldneverexist'");
449
		$this->assertNotNull($group);
450
		$this->assertEquals($group->Code, 'somegroupthatwouldneverexist');
451
		$this->assertEquals($group->Title, 'New Group');
452
453
		$grouplessMember->removeFromGroupByCode('memberless');
454
		$this->assertEquals($memberlessGroup->Members()->Count(), 0);
455
		$this->assertEquals($grouplessMember->Groups()->Count(), 1);
456
457
		$grouplessMember->removeFromGroupByCode('somegroupthatwouldneverexist');
458
		$this->assertEquals($grouplessMember->Groups()->Count(), 0);
459
	}
460
461
	public function testInGroup() {
462
		$staffmember = $this->objFromFixture('Member', 'staffmember');
463
		$managementmember = $this->objFromFixture('Member', 'managementmember');
464
		$accountingmember = $this->objFromFixture('Member', 'accountingmember');
465
		$ceomember = $this->objFromFixture('Member', 'ceomember');
466
467
		$staffgroup = $this->objFromFixture('Group', 'staffgroup');
468
		$managementgroup = $this->objFromFixture('Group', 'managementgroup');
469
		$accountinggroup = $this->objFromFixture('Group', 'accountinggroup');
470
		$ceogroup = $this->objFromFixture('Group', 'ceogroup');
471
472
		$this->assertTrue(
473
			$staffmember->inGroup($staffgroup),
474
			'Direct group membership is detected'
475
		);
476
		$this->assertTrue(
477
			$managementmember->inGroup($staffgroup),
478
			'Users of child group are members of a direct parent group (if not in strict mode)'
479
		);
480
		$this->assertTrue(
481
			$accountingmember->inGroup($staffgroup),
482
			'Users of child group are members of a direct parent group (if not in strict mode)'
483
		);
484
		$this->assertTrue(
485
			$ceomember->inGroup($staffgroup),
486
			'Users of indirect grandchild group are members of a parent group (if not in strict mode)'
487
		);
488
		$this->assertTrue(
489
			$ceomember->inGroup($ceogroup, true),
490
			'Direct group membership is dected (if in strict mode)'
491
		);
492
		$this->assertFalse(
493
			$ceomember->inGroup($staffgroup, true),
494
			'Users of child group are not members of a direct parent group (if in strict mode)'
495
		);
496
		$this->assertFalse(
497
			$staffmember->inGroup($managementgroup),
498
			'Users of parent group are not members of a direct child group'
499
		);
500
		$this->assertFalse(
501
			$staffmember->inGroup($ceogroup),
502
			'Users of parent group are not members of an indirect grandchild group'
503
		);
504
		$this->assertFalse(
505
			$accountingmember->inGroup($managementgroup),
506
			'Users of group are not members of any siblings'
507
		);
508
		$this->assertFalse(
509
			$staffmember->inGroup('does-not-exist'),
510
			'Non-existant group returns false'
511
		);
512
	}
513
514
	/**
515
	 * Tests that the user is able to view their own record, and in turn, they can
516
	 * edit and delete their own record too.
517
	 */
518
	public function testCanManipulateOwnRecord() {
519
		$extensions = $this->removeExtensions(SS_Object::get_extensions('Member'));
520
		$member = $this->objFromFixture('Member', 'test');
521
		$member2 = $this->objFromFixture('Member', 'staffmember');
522
523
		$this->session()->inst_set('loggedInAs', null);
524
525
		/* Not logged in, you can't view, delete or edit the record */
526
		$this->assertFalse($member->canView());
527
		$this->assertFalse($member->canDelete());
528
		$this->assertFalse($member->canEdit());
529
530
		/* Logged in users can edit their own record */
531
		$this->session()->inst_set('loggedInAs', $member->ID);
532
		$this->assertTrue($member->canView());
533
		$this->assertFalse($member->canDelete());
534
		$this->assertTrue($member->canEdit());
535
536
		/* Other uses cannot view, delete or edit others records */
537
		$this->session()->inst_set('loggedInAs', $member2->ID);
538
		$this->assertFalse($member->canView());
539
		$this->assertFalse($member->canDelete());
540
		$this->assertFalse($member->canEdit());
541
542
		$this->addExtensions($extensions);
543
		$this->session()->inst_set('loggedInAs', null);
544
	}
545
546
	public function testAuthorisedMembersCanManipulateOthersRecords() {
547
		$extensions = $this->removeExtensions(SS_Object::get_extensions('Member'));
548
		$member = $this->objFromFixture('Member', 'test');
549
		$member2 = $this->objFromFixture('Member', 'staffmember');
550
551
		/* Group members with SecurityAdmin permissions can manipulate other records */
552
		$this->session()->inst_set('loggedInAs', $member->ID);
553
		$this->assertTrue($member2->canView());
554
		$this->assertTrue($member2->canDelete());
555
		$this->assertTrue($member2->canEdit());
556
557
		$this->addExtensions($extensions);
558
		$this->session()->inst_set('loggedInAs', null);
559
	}
560
561
	public function testExtendedCan() {
562
		$extensions = $this->removeExtensions(SS_Object::get_extensions('Member'));
563
		$member = $this->objFromFixture('Member', 'test');
564
565
		/* Normal behaviour is that you can't view a member unless canView() on an extension returns true */
566
		$this->assertFalse($member->canView());
567
		$this->assertFalse($member->canDelete());
568
		$this->assertFalse($member->canEdit());
569
570
		/* Apply a extension that allows viewing in any case (most likely the case for member profiles) */
571
		Member::add_extension('MemberTest_ViewingAllowedExtension');
572
		$member2 = $this->objFromFixture('Member', 'staffmember');
573
574
		$this->assertTrue($member2->canView());
575
		$this->assertFalse($member2->canDelete());
576
		$this->assertFalse($member2->canEdit());
577
578
		/* Apply a extension that denies viewing of the Member */
579
		Member::remove_extension('MemberTest_ViewingAllowedExtension');
580
		Member::add_extension('MemberTest_ViewingDeniedExtension');
581
		$member3 = $this->objFromFixture('Member', 'managementmember');
582
583
		$this->assertFalse($member3->canView());
584
		$this->assertFalse($member3->canDelete());
585
		$this->assertFalse($member3->canEdit());
586
587
		/* Apply a extension that allows viewing and editing but denies deletion */
588
		Member::remove_extension('MemberTest_ViewingDeniedExtension');
589
		Member::add_extension('MemberTest_EditingAllowedDeletingDeniedExtension');
590
		$member4 = $this->objFromFixture('Member', 'accountingmember');
591
592
		$this->assertTrue($member4->canView());
593
		$this->assertFalse($member4->canDelete());
594
		$this->assertTrue($member4->canEdit());
595
596
		Member::remove_extension('MemberTest_EditingAllowedDeletingDeniedExtension');
597
		$this->addExtensions($extensions);
598
	}
599
600
	/**
601
	 * Tests for {@link Member::getName()} and {@link Member::setName()}
602
	 */
603
	public function testName() {
604
		$member = $this->objFromFixture('Member', 'test');
605
		$member->setName('Test Some User');
606
		$this->assertEquals('Test Some User', $member->getName());
607
		$member->setName('Test');
608
		$this->assertEquals('Test', $member->getName());
609
		$member->FirstName = 'Test';
0 ignored issues
show
Documentation introduced by
The property FirstName does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
610
		$member->Surname = '';
0 ignored issues
show
Documentation introduced by
The property Surname does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
611
		$this->assertEquals('Test', $member->getName());
612
	}
613
614
	public function testMembersWithSecurityAdminAccessCantEditAdminsUnlessTheyreAdminsThemselves() {
615
		$adminMember = $this->objFromFixture('Member', 'admin');
616
		$otherAdminMember = $this->objFromFixture('Member', 'other-admin');
617
		$securityAdminMember = $this->objFromFixture('Member', 'test');
618
		$ceoMember = $this->objFromFixture('Member', 'ceomember');
619
620
		// Careful: Don't read as english language.
621
		// More precisely this should read canBeEditedBy()
622
623
		$this->assertTrue($adminMember->canEdit($adminMember), 'Admins can edit themselves');
0 ignored issues
show
Bug introduced by
It seems like $adminMember defined by $this->objFromFixture('Member', 'admin') on line 615 can also be of type object<DataObject>; however, DataObject::canEdit() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
624
		$this->assertTrue($otherAdminMember->canEdit($adminMember), 'Admins can edit other admins');
0 ignored issues
show
Bug introduced by
It seems like $adminMember defined by $this->objFromFixture('Member', 'admin') on line 615 can also be of type object<DataObject>; however, DataObject::canEdit() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
625
		$this->assertTrue($securityAdminMember->canEdit($adminMember), 'Admins can edit other members');
0 ignored issues
show
Bug introduced by
It seems like $adminMember defined by $this->objFromFixture('Member', 'admin') on line 615 can also be of type object<DataObject>; however, DataObject::canEdit() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
626
627
		$this->assertTrue($securityAdminMember->canEdit($securityAdminMember), 'Security-Admins can edit themselves');
0 ignored issues
show
Bug introduced by
It seems like $securityAdminMember defined by $this->objFromFixture('Member', 'test') on line 617 can also be of type object<DataObject>; however, DataObject::canEdit() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
628
		$this->assertFalse($adminMember->canEdit($securityAdminMember), 'Security-Admins can not edit other admins');
0 ignored issues
show
Bug introduced by
It seems like $securityAdminMember defined by $this->objFromFixture('Member', 'test') on line 617 can also be of type object<DataObject>; however, DataObject::canEdit() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
629
		$this->assertTrue($ceoMember->canEdit($securityAdminMember), 'Security-Admins can edit other members');
0 ignored issues
show
Bug introduced by
It seems like $securityAdminMember defined by $this->objFromFixture('Member', 'test') on line 617 can also be of type object<DataObject>; however, DataObject::canEdit() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
630
	}
631
632
	public function testOnChangeGroups() {
633
		$staffGroup = $this->objFromFixture('Group', 'staffgroup');
634
		$staffMember = $this->objFromFixture('Member', 'staffmember');
635
		$adminMember = $this->objFromFixture('Member', 'admin');
636
		$newAdminGroup = new Group(array('Title' => 'newadmin'));
637
		$newAdminGroup->write();
638
		Permission::grant($newAdminGroup->ID, 'ADMIN');
639
		$newOtherGroup = new Group(array('Title' => 'othergroup'));
640
		$newOtherGroup->write();
641
642
		$this->assertTrue(
643
			$staffMember->onChangeGroups(array($staffGroup->ID)),
644
			'Adding existing non-admin group relation is allowed for non-admin members'
645
		);
646
		$this->assertTrue(
647
			$staffMember->onChangeGroups(array($newOtherGroup->ID)),
648
			'Adding new non-admin group relation is allowed for non-admin members'
649
		);
650
		$this->assertFalse(
651
			$staffMember->onChangeGroups(array($newAdminGroup->ID)),
652
			'Adding new admin group relation is not allowed for non-admin members'
653
		);
654
655
		$this->session()->inst_set('loggedInAs', $adminMember->ID);
656
		$this->assertTrue(
657
			$staffMember->onChangeGroups(array($newAdminGroup->ID)),
658
			'Adding new admin group relation is allowed for normal users, when granter is logged in as admin'
659
		);
660
		$this->session()->inst_set('loggedInAs', null);
661
662
		$this->assertTrue(
663
			$adminMember->onChangeGroups(array($newAdminGroup->ID)),
664
			'Adding new admin group relation is allowed for admin members'
665
		);
666
	}
667
668
    /**
669
     * Ensure DirectGroups listbox disallows admin-promotion
670
     */
671
    public function testAllowedGroupsListbox() {
672
        /** @var Group $adminGroup */
673
        $adminGroup = $this->objFromFixture('Group', 'admingroup');
674
        /** @var Member $staffMember */
675
        $staffMember = $this->objFromFixture('Member', 'staffmember');
676
        /** @var Member $adminMember */
677
        $adminMember = $this->objFromFixture('Member', 'admin');
678
679
        // Ensure you can see the DirectGroups box
680
        $this->logInWithPermission('EDIT_PERMISSIONS');
681
682
        // Non-admin member field contains non-admin groups
683
        /** @var ListboxField $staffListbox */
684
        $staffListbox = $staffMember->getCMSFields()->dataFieldByName('DirectGroups');
685
        $this->assertArrayNotHasKey($adminGroup->ID, $staffListbox->getSource());
686
687
        // admin member field contains admin group
688
        /** @var ListboxField $adminListbox */
689
        $adminListbox = $adminMember->getCMSFields()->dataFieldByName('DirectGroups');
690
        $this->assertArrayHasKey($adminGroup->ID, $adminListbox->getSource());
691
692
        // If logged in as admin, staff listbox has admin group
693
        $this->logInWithPermission('ADMIN');
694
        $staffListbox = $staffMember->getCMSFields()->dataFieldByName('DirectGroups');
695
        $this->assertArrayHasKey($adminGroup->ID, $staffListbox->getSource());
696
    }
697
698
	/**
699
	 * Test Member_GroupSet::add
700
	 */
701
	public function testOnChangeGroupsByAdd() {
702
		$staffMember = $this->objFromFixture('Member', 'staffmember');
703
		$adminMember = $this->objFromFixture('Member', 'admin');
704
705
		// Setup new admin group
706
		$newAdminGroup = new Group(array('Title' => 'newadmin'));
707
		$newAdminGroup->write();
708
		Permission::grant($newAdminGroup->ID, 'ADMIN');
709
710
		// Setup non-admin group
711
		$newOtherGroup = new Group(array('Title' => 'othergroup'));
712
		$newOtherGroup->write();
713
714
		// Test staff can be added to other group
715
		$this->assertFalse($staffMember->inGroup($newOtherGroup));
716
		$staffMember->Groups()->add($newOtherGroup);
717
		$this->assertTrue(
718
			$staffMember->inGroup($newOtherGroup),
719
			'Adding new non-admin group relation is allowed for non-admin members'
720
		);
721
722
		// Test staff member can't be added to admin groups
723
		$this->assertFalse($staffMember->inGroup($newAdminGroup));
724
		$staffMember->Groups()->add($newAdminGroup);
725
		$this->assertFalse(
726
			$staffMember->inGroup($newAdminGroup),
727
			'Adding new admin group relation is not allowed for non-admin members'
728
		);
729
730
		// Test staff member can be added to admin group by admins
731
		$this->logInAs($adminMember);
732
		$staffMember->Groups()->add($newAdminGroup);
733
		$this->assertTrue(
734
			$staffMember->inGroup($newAdminGroup),
735
			'Adding new admin group relation is allowed for normal users, when granter is logged in as admin'
736
		);
737
738
		// Test staff member can be added if they are already admin
739
		$this->session()->inst_set('loggedInAs', null);
740
		$this->assertFalse($adminMember->inGroup($newAdminGroup));
741
		$adminMember->Groups()->add($newAdminGroup);
742
		$this->assertTrue(
743
			$adminMember->inGroup($newAdminGroup),
744
			'Adding new admin group relation is allowed for admin members'
745
		);
746
	}
747
748
	/**
749
	 * Test Member_GroupSet::add
750
	 */
751
	public function testOnChangeGroupsBySetIDList() {
752
		$staffMember = $this->objFromFixture('Member', 'staffmember');
753
754
		// Setup new admin group
755
		$newAdminGroup = new Group(array('Title' => 'newadmin'));
756
		$newAdminGroup->write();
757
		Permission::grant($newAdminGroup->ID, 'ADMIN');
758
759
		// Test staff member can't be added to admin groups
760
		$this->assertFalse($staffMember->inGroup($newAdminGroup));
761
		$staffMember->Groups()->setByIDList(array($newAdminGroup->ID));
762
		$this->assertFalse(
763
			$staffMember->inGroup($newAdminGroup),
764
			'Adding new admin group relation is not allowed for non-admin members'
765
		);
766
	}
767
768
	/**
769
	 * Test that extensions using updateCMSFields() are applied correctly
770
	 */
771
	public function testUpdateCMSFields() {
772
		Member::add_extension('MemberTest_FieldsExtension');
773
774
		$member = singleton('Member');
775
		$fields = $member->getCMSFields();
776
777
		$this->assertNotNull($fields->dataFieldByName('Email'), 'Scaffolded fields are retained');
778
		$this->assertNull($fields->dataFieldByName('Salt'), 'Field modifications run correctly');
779
		$this->assertNotNull($fields->dataFieldByName('TestMemberField'), 'Extension is applied correctly');
780
781
		Member::remove_extension('MemberTest_FieldsExtension');
782
	}
783
784
	/**
785
	 * Test that all members are returned
786
	 */
787
	public function testMap_in_groupsReturnsAll() {
788
		$members = Member::map_in_groups();
789
		$this->assertEquals(13, count($members), 'There are 12 members in the mock plus a fake admin');
790
	}
791
792
	/**
793
	 * Test that only admin members are returned
794
	 */
795
	public function testMap_in_groupsReturnsAdmins() {
796
		$adminID = $this->objFromFixture('Group', 'admingroup')->ID;
797
		$members = Member::map_in_groups($adminID);
798
799
		$admin = $this->objFromFixture('Member', 'admin');
800
		$otherAdmin = $this->objFromFixture('Member', 'other-admin');
801
802
		$this->assertTrue(in_array($admin->getTitle(), $members),
803
			$admin->getTitle().' should be in the returned list.');
804
		$this->assertTrue(in_array($otherAdmin->getTitle(), $members),
805
			$otherAdmin->getTitle().' should be in the returned list.');
806
		$this->assertEquals(2, count($members), 'There should be 2 members from the admin group');
807
	}
808
809
	/**
810
	 * Add the given array of member extensions as class names.
811
	 * This is useful for re-adding extensions after being removed
812
	 * in a test case to produce an unbiased test.
813
	 *
814
	 * @param array $extensions
815
	 * @return array The added extensions
816
	 */
817
	protected function addExtensions($extensions) {
818
		if($extensions) foreach($extensions as $extension) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $extensions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
819
			Member::add_extension($extension);
820
		}
821
		return $extensions;
822
	}
823
824
	/**
825
	 * Remove given extensions from Member. This is useful for
826
	 * removing extensions that could produce a biased
827
	 * test result, as some extensions applied by project
828
	 * code or modules can do this.
829
	 *
830
	 * @param array $extensions
831
	 * @return array The removed extensions
832
	 */
833
	protected function removeExtensions($extensions) {
834
		if($extensions) foreach($extensions as $extension) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $extensions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
835
			Member::remove_extension($extension);
836
		}
837
		return $extensions;
838
	}
839
840
	public function testGenerateAutologinTokenAndStoreHash() {
841
		$enc = new PasswordEncryptor_Blowfish();
842
843
		$m = new Member();
844
		$m->PasswordEncryption = 'blowfish';
845
		$m->Salt = $enc->salt('123');
846
847
		$token = $m->generateAutologinTokenAndStoreHash();
848
849
		$this->assertEquals($m->encryptWithUserSettings($token), $m->AutoLoginHash, 'Stores the token as ahash.');
850
	}
851
852
	public function testValidateAutoLoginToken() {
853
		$enc = new PasswordEncryptor_Blowfish();
854
855
		$m1 = new Member();
856
		$m1->PasswordEncryption = 'blowfish';
857
		$m1->Salt = $enc->salt('123');
858
		$m1Token = $m1->generateAutologinTokenAndStoreHash();
859
860
		$m2 = new Member();
861
		$m2->PasswordEncryption = 'blowfish';
862
		$m2->Salt = $enc->salt('456');
863
		$m2Token = $m2->generateAutologinTokenAndStoreHash();
864
865
		$this->assertTrue($m1->validateAutoLoginToken($m1Token), 'Passes token validity test against matching member.');
866
		$this->assertFalse($m2->validateAutoLoginToken($m1Token), 'Fails token validity test against other member.');
867
	}
868
869
	public function testCanDelete() {
870
		$admin1 = $this->objFromFixture('Member', 'admin');
871
		$admin2 = $this->objFromFixture('Member', 'other-admin');
872
		$member1 = $this->objFromFixture('Member', 'grouplessmember');
873
		$member2 = $this->objFromFixture('Member', 'noformatmember');
874
875
		$this->assertTrue(
876
			$admin1->canDelete($admin2),
0 ignored issues
show
Bug introduced by
It seems like $admin2 defined by $this->objFromFixture('Member', 'other-admin') on line 871 can also be of type object<DataObject>; however, DataObject::canDelete() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
877
			'Admins can delete other admins'
878
		);
879
		$this->assertTrue(
880
			$member1->canDelete($admin2),
0 ignored issues
show
Bug introduced by
It seems like $admin2 defined by $this->objFromFixture('Member', 'other-admin') on line 871 can also be of type object<DataObject>; however, DataObject::canDelete() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
881
			'Admins can delete non-admins'
882
		);
883
		$this->assertFalse(
884
			$admin1->canDelete($admin1),
0 ignored issues
show
Bug introduced by
It seems like $admin1 defined by $this->objFromFixture('Member', 'admin') on line 870 can also be of type object<DataObject>; however, DataObject::canDelete() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
885
			'Admins can not delete themselves'
886
		);
887
		$this->assertFalse(
888
			$member1->canDelete($member2),
0 ignored issues
show
Bug introduced by
It seems like $member2 defined by $this->objFromFixture('Member', 'noformatmember') on line 873 can also be of type object<DataObject>; however, DataObject::canDelete() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
889
			'Non-admins can not delete other non-admins'
890
		);
891
		$this->assertFalse(
892
			$member1->canDelete($member1),
0 ignored issues
show
Bug introduced by
It seems like $member1 defined by $this->objFromFixture('M...er', 'grouplessmember') on line 872 can also be of type object<DataObject>; however, DataObject::canDelete() does only seem to accept object<Member>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
893
			'Non-admins can not delete themselves'
894
		);
895
	}
896
897
	public function testFailedLoginCount() {
898
		$maxFailedLoginsAllowed = 3;
899
		//set up the config variables to enable login lockouts
900
		Config::inst()->update('Member', 'lock_out_after_incorrect_logins', $maxFailedLoginsAllowed);
901
902
		$member = $this->objFromFixture('Member', 'test');
903
		$failedLoginCount = $member->FailedLoginCount;
0 ignored issues
show
Documentation introduced by
The property FailedLoginCount does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
904
905
		for ($i = 1; $i < $maxFailedLoginsAllowed; ++$i) {
906
			$member->registerFailedLogin();
907
908
			$this->assertEquals(
909
				++$failedLoginCount,
910
				$member->FailedLoginCount,
911
				'Failed to increment $member->FailedLoginCount'
912
			);
913
914
			$this->assertFalse(
915
				$member->isLockedOut(),
916
				"Member has been locked out too early"
917
			);
918
		}
919
	}
920
921
	public function testMemberValidator()
922
	{
923
		// clear custom requirements for this test
924
		Config::inst()->update('Member_Validator', 'customRequired', null);
925
		$memberA = $this->objFromFixture('Member', 'admin');
926
		$memberB = $this->objFromFixture('Member', 'test');
927
928
		// create a blank form
929
		$form = new MemberTest_ValidatorForm();
930
931
		$validator = new Member_Validator();
932
		$validator->setForm($form);
933
934
		// Simulate creation of a new member via form, but use an existing member identifier
935
		$fail = $validator->php(array(
936
			'FirstName' => 'Test',
937
			'Email' => $memberA->Email
938
		));
939
940
		$this->assertFalse(
941
			$fail,
942
			'Member_Validator must fail when trying to create new Member with existing Email.'
943
		);
944
945
		// populate the form with values from another member
946
		$form->loadDataFrom($memberB);
0 ignored issues
show
Bug introduced by
It seems like $memberB defined by $this->objFromFixture('Member', 'test') on line 926 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...
947
948
		// Assign the validator to an existing member
949
		// (this is basically the same as passing the member ID with the form data)
950
		$validator->setForMember($memberB);
951
952
		// Simulate update of a member via form and use an existing member Email
953
		$fail = $validator->php(array(
954
			'FirstName' => 'Test',
955
			'Email' => $memberA->Email
956
		));
957
958
		// Simulate update to a new Email address
959
		$pass1 = $validator->php(array(
960
			'FirstName' => 'Test',
961
			'Email' => '[email protected]'
962
		));
963
964
		// Pass in the same Email address that the member already has. Ensure that case is valid
965
		$pass2 = $validator->php(array(
966
			'FirstName' => 'Test',
967
			'Surname' => 'User',
968
			'Email' => $memberB->Email
969
		));
970
971
		$this->assertFalse(
972
			$fail,
973
			'Member_Validator must fail when trying to update existing member with existing Email.'
974
		);
975
976
		$this->assertTrue(
977
			$pass1,
978
			'Member_Validator must pass when Email is updated to a value that\'s not in use.'
979
		);
980
981
		$this->assertTrue(
982
			$pass2,
983
			'Member_Validator must pass when Member updates his own Email to the already existing value.'
984
		);
985
	}
986
987
	public function testMemberValidatorWithExtensions()
988
	{
989
		// clear custom requirements for this test
990
		Config::inst()->update('Member_Validator', 'customRequired', null);
991
992
		// create a blank form
993
		$form = new MemberTest_ValidatorForm();
994
995
		// Test extensions
996
		Member_Validator::add_extension('MemberTest_MemberValidator_SurnameMustMatchFirstNameExtension');
997
		$validator = new Member_Validator();
998
		$validator->setForm($form);
999
1000
		// This test should fail, since the extension enforces FirstName == Surname
1001
		$fail = $validator->php(array(
1002
			'FirstName' => 'Test',
1003
			'Surname' => 'User',
1004
			'Email' => '[email protected]'
1005
		));
1006
1007
		$pass = $validator->php(array(
1008
			'FirstName' => 'Test',
1009
			'Surname' => 'Test',
1010
			'Email' => '[email protected]'
1011
		));
1012
1013
		$this->assertFalse(
1014
			$fail,
1015
			'Member_Validator must fail because of added extension.'
1016
		);
1017
1018
		$this->assertTrue(
1019
			$pass,
1020
			'Member_Validator must succeed, since it meets all requirements.'
1021
		);
1022
1023
		// Add another extension that always fails. This ensures that all extensions are considered in the validation
1024
		Member_Validator::add_extension('MemberTest_MemberValidator_AlwaysFailsExtension');
1025
		$validator = new Member_Validator();
1026
		$validator->setForm($form);
1027
1028
		// Even though the data is valid, This test should still fail, since one extension always returns false
1029
		$fail = $validator->php(array(
1030
			'FirstName' => 'Test',
1031
			'Surname' => 'Test',
1032
			'Email' => '[email protected]'
1033
		));
1034
1035
		$this->assertFalse(
1036
			$fail,
1037
			'Member_Validator must fail because of added extensions.'
1038
		);
1039
1040
		// Remove added extensions
1041
		Member_Validator::remove_extension('MemberTest_MemberValidator_AlwaysFailsExtension');
1042
		Member_Validator::remove_extension('MemberTest_MemberValidator_SurnameMustMatchFirstNameExtension');
1043
	}
1044
1045
	public function testCustomMemberValidator()
1046
	{
1047
		// clear custom requirements for this test
1048
		Config::inst()->update('Member_Validator', 'customRequired', null);
1049
1050
		$member = $this->objFromFixture('Member', 'admin');
1051
1052
		$form = new MemberTest_ValidatorForm();
1053
		$form->loadDataFrom($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by $this->objFromFixture('Member', 'admin') on line 1050 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...
1054
1055
		$validator = new Member_Validator();
1056
		$validator->setForm($form);
1057
1058
		$pass = $validator->php(array(
1059
			'FirstName' => 'Borris',
1060
			'Email' => '[email protected]'
1061
		));
1062
1063
		$fail = $validator->php(array(
1064
			'Email' => '[email protected]',
1065
			'Surname' => ''
1066
		));
1067
1068
		$this->assertTrue($pass, 'Validator requires a FirstName and Email');
1069
		$this->assertFalse($fail, 'Missing FirstName');
1070
1071
		$ext = new MemberTest_ValidatorExtension();
1072
		$ext->updateValidator($validator);
1073
1074
		$pass = $validator->php(array(
1075
			'FirstName' => 'Borris',
1076
			'Email' => '[email protected]'
1077
		));
1078
1079
		$fail = $validator->php(array(
1080
			'Email' => '[email protected]'
1081
		));
1082
1083
		$this->assertFalse($pass, 'Missing surname');
1084
		$this->assertFalse($fail, 'Missing surname value');
1085
1086
		$fail = $validator->php(array(
1087
			'Email' => '[email protected]',
1088
			'Surname' => 'Silverman'
1089
		));
1090
1091
		$this->assertTrue($fail, 'Passes with email and surname now (no firstname)');
1092
	}
1093
1094
	public function testCurrentUser() {
1095
		$this->assertNull(Member::currentUser());
1096
1097
		$adminMember = $this->objFromFixture('Member', 'admin');
1098
		$this->logInAs($adminMember);
1099
1100
		$userFromSession = Member::currentUser();
1101
		$this->assertEquals($adminMember->ID, $userFromSession->ID);
1102
	}
1103
1104
}
1105
1106
/**
1107
 * @package framework
1108
 * @subpackage tests
1109
 */
1110
class MemberTest_ValidatorForm extends Form implements TestOnly {
1111
1112
	public function __construct() {
1113
		parent::__construct(Controller::curr(), __CLASS__, new FieldList(
1114
			new TextField('Email'),
1115
			new TextField('Surname'),
1116
			new TextField('ID'),
1117
			new TextField('FirstName')
1118
		), new FieldList(
1119
			new FormAction('someAction')
1120
		));
1121
	}
1122
}
1123
1124
/**
1125
 * @package framework
1126
 * @subpackage tests
1127
 */
1128
class MemberTest_ValidatorExtension extends DataExtension implements TestOnly {
1129
1130
	public function updateValidator(&$validator) {
1131
		$validator->addRequiredField('Surname');
1132
		$validator->removeRequiredField('FirstName');
1133
	}
1134
}
1135
1136
/**
1137
 * Extension that adds additional validation criteria
1138
 * @package framework
1139
 * @subpackage tests
1140
 */
1141
class MemberTest_MemberValidator_SurnameMustMatchFirstNameExtension extends DataExtension implements TestOnly
1142
{
1143
	public function updatePHP($data, $form) {
0 ignored issues
show
Unused Code introduced by
The parameter $form 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...
1144
		return $data['FirstName'] == $data['Surname'];
1145
	}
1146
}
1147
1148
/**
1149
 * Extension that adds additional validation criteria
1150
 * @package framework
1151
 * @subpackage tests
1152
 */
1153
class MemberTest_MemberValidator_AlwaysFailsExtension extends DataExtension implements TestOnly
1154
{
1155
	public function updatePHP($data, $form) {
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 $form 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...
1156
		return false;
1157
	}
1158
}
1159
1160
/**
1161
 * @package framework
1162
 * @subpackage tests
1163
 */
1164
class MemberTest_ViewingAllowedExtension extends DataExtension implements TestOnly {
1165
1166
	public function canView($member = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $member 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...
1167
		return true;
1168
	}
1169
}
1170
1171
/**
1172
 * @package framework
1173
 * @subpackage tests
1174
 */
1175
class MemberTest_ViewingDeniedExtension extends DataExtension implements TestOnly {
1176
1177
	public function canView($member = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $member 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...
1178
		return false;
1179
	}
1180
}
1181
1182
/**
1183
 * @package framework
1184
 * @subpackage tests
1185
 */
1186
class MemberTest_FieldsExtension extends DataExtension implements TestOnly {
1187
1188
	public function updateCMSFields(FieldList $fields) {
1189
		$fields->addFieldToTab('Root.Main', new TextField('TestMemberField', 'Test'));
1190
	}
1191
1192
}
1193
1194
/**
1195
 * @package framework
1196
 * @subpackage tests
1197
 */
1198
class MemberTest_EditingAllowedDeletingDeniedExtension extends DataExtension implements TestOnly {
1199
1200
	public function canView($member = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $member 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...
1201
		return true;
1202
	}
1203
1204
	public function canEdit($member = null) {
1205
		return true;
1206
	}
1207
1208
	public function canDelete($member = null) {
1209
		return false;
1210
	}
1211
1212
}
1213
1214
/**
1215
 * @package framework
1216
 * @subpackage tests
1217
 */
1218
class MemberTest_PasswordValidator extends PasswordValidator {
1219
	public function __construct() {
1220
		parent::__construct();
1221
		$this->minLength(7);
1222
		$this->checkHistoricalPasswords(6);
1223
		$this->characterStrength(3, array('lowercase','uppercase','digits','punctuation'));
1224
	}
1225
1226
}
1227