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

MemberTest   D

Complexity

Total Complexity 45

Size/Duplication

Total Lines 1013
Duplicated Lines 2.07 %

Coupling/Cohesion

Components 2
Dependencies 19

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 45
c 1
b 0
f 1
lcom 2
cbo 19
dl 21
loc 1013
rs 4.7438

40 Methods

Rating   Name   Duplication   Size   Complexity  
A __destruct() 0 3 1
A tearDown() 0 4 1
A testIsPasswordExpired() 0 22 1
A testMap_in_groupsReturnsAll() 0 4 1
A __construct() 0 9 1
A setUp() 0 7 1
A testWriteDoesntMergeNewRecordWithExistingMember() 9 9 1
A testWriteDoesntMergeExistingMemberOnIdentifierChange() 12 12 1
A testDefaultPasswordEncryptionOnMember() 0 17 1
A testDefaultPasswordEncryptionDoesntChangeExistingMembers() 0 21 1
A testKeepsEncryptionOnEmptyPasswords() 0 16 1
A testSetPassword() 0 7 1
B testPasswordChangeLogging() 0 36 1
A testChangedPasswordEmaling() 0 12 1
B testValidatePassword() 0 78 1
A testPasswordExpirySetting() 0 17 1
A testMemberWithNoDateFormatFallsbackToGlobalLocaleDefaultFormat() 0 7 1
B testInGroups() 0 24 1
A testAddToGroupByCode() 0 23 1
B testRemoveFromGroupByCode() 0 27 1
A testInGroup() 0 52 1
B testCanManipulateOwnRecord() 0 27 1
A testAuthorisedMembersCanManipulateOthersRecords() 0 14 1
B testExtendedCan() 0 38 1
A testName() 0 10 1
A testMembersWithSecurityAdminAccessCantEditAdminsUnlessTheyreAdminsThemselves() 0 17 1
B testOnChangeGroups() 0 35 1
B testOnChangeGroupsByAdd() 0 46 1
A testOnChangeGroupsBySetIDList() 0 16 1
A testUpdateCMSFields() 0 12 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
B testCanDelete() 0 27 1
B testFailedLoginCount() 0 24 2
A testMemberValidator() 0 65 1
A testMemberValidatorWithExtensions() 0 57 1
A testCustomMemberValidator() 0 48 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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
49
50
	/**
51
	 * @expectedException ValidationException
52
	 */
53 View Code Duplication
	public function testWriteDoesntMergeNewRecordWithExistingMember() {
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...
54
		$m1 = new Member();
55
		$m1->Email = '[email protected]';
56
		$m1->write();
57
58
		$m2 = new Member();
59
		$m2->Email = '[email protected]';
60
		$m2->write();
61
	}
62
63
	/**
64
	 * @expectedException ValidationException
65
	 */
66 View Code Duplication
	public function testWriteDoesntMergeExistingMemberOnIdentifierChange() {
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...
67
		$m1 = new Member();
68
		$m1->Email = '[email protected]';
69
		$m1->write();
70
71
		$m2 = new Member();
72
		$m2->Email = '[email protected]';
73
		$m2->write();
74
75
		$m2->Email = '[email protected]';
76
		$m2->write();
77
	}
78
79
	public function testDefaultPasswordEncryptionOnMember() {
80
		$memberWithPassword = new Member();
81
		$memberWithPassword->Password = 'mypassword';
82
		$memberWithPassword->write();
83
		$this->assertEquals(
84
			$memberWithPassword->PasswordEncryption,
85
			Security::config()->password_encryption_algorithm,
86
			'Password encryption is set for new member records on first write (with setting "Password")'
87
		);
88
89
		$memberNoPassword = new Member();
90
		$memberNoPassword->write();
91
		$this->assertNull(
92
			$memberNoPassword->PasswordEncryption,
93
			'Password encryption is not set for new member records on first write, when not setting a "Password")'
94
		);
95
	}
96
97
	public function testDefaultPasswordEncryptionDoesntChangeExistingMembers() {
98
		$member = new Member();
99
		$member->Password = 'mypassword';
100
		$member->PasswordEncryption = 'sha1_v2.4';
101
		$member->write();
102
103
		$origAlgo = Security::config()->password_encryption_algorithm;
0 ignored issues
show
Documentation introduced by
The property password_encryption_algorithm 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...
104
		Security::config()->password_encryption_algorithm = 'none';
0 ignored issues
show
Documentation introduced by
The property password_encryption_algorithm 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...
105
106
		$member->Password = 'mynewpassword';
107
		$member->write();
108
109
		$this->assertEquals(
110
			$member->PasswordEncryption,
111
			'sha1_v2.4'
112
		);
113
		$result = $member->checkPassword('mynewpassword');
114
		$this->assertTrue($result->valid());
115
116
		Security::config()->password_encryption_algorithm = $origAlgo;
0 ignored issues
show
Documentation introduced by
The property password_encryption_algorithm 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...
117
	}
118
119
	public function testKeepsEncryptionOnEmptyPasswords() {
120
		$member = new Member();
121
		$member->Password = 'mypassword';
122
		$member->PasswordEncryption = 'sha1_v2.4';
123
		$member->write();
124
125
		$member->Password = '';
126
		$member->write();
127
128
		$this->assertEquals(
129
			$member->PasswordEncryption,
130
			'sha1_v2.4'
131
		);
132
		$result = $member->checkPassword('');
133
		$this->assertTrue($result->valid());
134
	}
135
136
	public function testSetPassword() {
137
		$member = $this->objFromFixture('Member', 'test');
138
		$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...
139
		$member->write();
140
		$result = $member->checkPassword('test1');
141
		$this->assertTrue($result->valid());
142
	}
143
144
	/**
145
	 * Test that password changes are logged properly
146
	 */
147
	public function testPasswordChangeLogging() {
148
		$member = $this->objFromFixture('Member', 'test');
149
		$this->assertNotNull($member);
150
		$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...
151
		$member->write();
152
153
		$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...
154
		$member->write();
155
156
		$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...
157
		$member->write();
158
159
		$passwords = DataObject::get("MemberPassword", "\"MemberID\" = $member->ID", "\"Created\" DESC, \"ID\" DESC")
160
			->getIterator();
161
		$this->assertNotNull($passwords);
162
		$passwords->rewind();
163
		$this->assertTrue($passwords->current()->checkPassword('test3'), "Password test3 not found in MemberRecord");
164
165
		$passwords->next();
166
		$this->assertTrue($passwords->current()->checkPassword('test2'), "Password test2 not found in MemberRecord");
167
168
		$passwords->next();
169
		$this->assertTrue($passwords->current()->checkPassword('test1'), "Password test1 not found in MemberRecord");
170
171
		$passwords->next();
172
		$this->assertInstanceOf('DataObject', $passwords->current());
173
		$this->assertTrue($passwords->current()->checkPassword('1nitialPassword'),
174
			"Password 1nitialPassword not found in MemberRecord");
175
176
		//check we don't retain orphaned records when a member is deleted
177
		$member->delete();
178
179
		$passwords = MemberPassword::get()->filter('MemberID', $member->OldID);
180
181
		$this->assertCount(0, $passwords);
182
	}
183
184
	/**
185
	 * Test that changed passwords will send an email
186
	 */
187
	public function testChangedPasswordEmaling() {
188
		$this->clearEmails();
189
190
		$member = $this->objFromFixture('Member', 'test');
191
		$this->assertNotNull($member);
192
		$valid = $member->changePassword('32asDF##$$%%');
193
		$this->assertTrue($valid->valid());
194
		/*
195
		$this->assertEmailSent("[email protected]", null, "/changed password/",
196
		'/sam@silverstripe\.com.*32asDF##\$\$%%/');
197
		*/
198
	}
199
200
	/**
201
	 * Test that passwords validate against NZ e-government guidelines
202
	 *  - don't allow the use of the last 6 passwords
203
	 *  - require at least 3 of lowercase, uppercase, digits and punctuation
204
	 *  - at least 7 characters long
205
	 */
206
	public function testValidatePassword() {
207
		$member = $this->objFromFixture('Member', 'test');
208
		$this->assertNotNull($member);
209
210
		Member::set_password_validator(new MemberTest_PasswordValidator());
211
212
		// BAD PASSWORDS
213
214
		$valid = $member->changePassword('shorty');
215
		$this->assertFalse($valid->valid());
216
		$this->assertContains("TOO_SHORT", $valid->codeList());
217
218
		$valid = $member->changePassword('longone');
219
		$this->assertNotContains("TOO_SHORT", $valid->codeList());
220
		$this->assertContains("LOW_CHARACTER_STRENGTH", $valid->codeList());
221
		$this->assertFalse($valid->valid());
222
223
		$valid = $member->changePassword('w1thNumb3rs');
224
		$this->assertNotContains("LOW_CHARACTER_STRENGTH", $valid->codeList());
225
		$this->assertTrue($valid->valid());
226
227
		// Clear out the MemberPassword table to ensure that the system functions properly in that situation
228
		DB::query("DELETE FROM \"MemberPassword\"");
229
230
		// GOOD PASSWORDS
231
232
		$valid = $member->changePassword('withSym###Ls');
233
		$this->assertNotContains("LOW_CHARACTER_STRENGTH", $valid->codeList());
234
		$this->assertTrue($valid->valid());
235
236
		$valid = $member->changePassword('withSym###Ls2');
237
		$this->assertTrue($valid->valid());
238
239
		$valid = $member->changePassword('withSym###Ls3');
240
		$this->assertTrue($valid->valid());
241
242
		$valid = $member->changePassword('withSym###Ls4');
243
		$this->assertTrue($valid->valid());
244
245
		$valid = $member->changePassword('withSym###Ls5');
246
		$this->assertTrue($valid->valid());
247
248
		$valid = $member->changePassword('withSym###Ls6');
249
		$this->assertTrue($valid->valid());
250
251
		$valid = $member->changePassword('withSym###Ls7');
252
		$this->assertTrue($valid->valid());
253
254
		// CAN'T USE PASSWORDS 2-7, but I can use pasword 1
255
256
		$valid = $member->changePassword('withSym###Ls2');
257
		$this->assertFalse($valid->valid());
258
		$this->assertContains("PREVIOUS_PASSWORD", $valid->codeList());
259
260
		$valid = $member->changePassword('withSym###Ls5');
261
		$this->assertFalse($valid->valid());
262
		$this->assertContains("PREVIOUS_PASSWORD", $valid->codeList());
263
264
		$valid = $member->changePassword('withSym###Ls7');
265
		$this->assertFalse($valid->valid());
266
		$this->assertContains("PREVIOUS_PASSWORD", $valid->codeList());
267
268
		$valid = $member->changePassword('withSym###Ls');
269
		$this->assertTrue($valid->valid());
270
271
		// HAVING DONE THAT, PASSWORD 2 is now available from the list
272
273
		$valid = $member->changePassword('withSym###Ls2');
274
		$this->assertTrue($valid->valid());
275
276
		$valid = $member->changePassword('withSym###Ls3');
277
		$this->assertTrue($valid->valid());
278
279
		$valid = $member->changePassword('withSym###Ls4');
280
		$this->assertTrue($valid->valid());
281
282
		Member::set_password_validator(null);
283
	}
284
285
	/**
286
	 * Test that the PasswordExpiry date is set when passwords are changed
287
	 */
288
	public function testPasswordExpirySetting() {
289
		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...
290
291
		$member = $this->objFromFixture('Member', 'test');
292
		$this->assertNotNull($member);
293
		$valid = $member->changePassword("Xx?1234234");
294
		$this->assertTrue($valid->valid());
295
296
		$expiryDate = date('Y-m-d', time() + 90*86400);
297
		$this->assertEquals($expiryDate, $member->PasswordExpiry);
298
299
		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...
300
		$valid = $member->changePassword("Xx?1234235");
301
		$this->assertTrue($valid->valid());
302
303
		$this->assertNull($member->PasswordExpiry);
304
	}
305
306
	public function testIsPasswordExpired() {
307
		$member = $this->objFromFixture('Member', 'test');
308
		$this->assertNotNull($member);
309
		$this->assertFalse($member->isPasswordExpired());
310
311
		$member = $this->objFromFixture('Member', 'noexpiry');
312
		$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...
313
		$this->assertFalse($member->isPasswordExpired());
314
315
		$member = $this->objFromFixture('Member', 'expiredpassword');
316
		$this->assertTrue($member->isPasswordExpired());
317
318
		// Check the boundary conditions
319
		// If PasswordExpiry == today, then it's expired
320
		$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...
321
		$this->assertTrue($member->isPasswordExpired());
322
323
		// If PasswordExpiry == tomorrow, then it's not
324
		$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...
325
		$this->assertFalse($member->isPasswordExpired());
326
327
	}
328
329
	public function testMemberWithNoDateFormatFallsbackToGlobalLocaleDefaultFormat() {
330
		Config::inst()->update('i18n', 'date_format', 'yyyy-MM-dd');
331
		Config::inst()->update('i18n', 'time_format', 'H:mm');
332
		$member = $this->objFromFixture('Member', 'noformatmember');
333
		$this->assertEquals('yyyy-MM-dd', $member->DateFormat);
334
		$this->assertEquals('H:mm', $member->TimeFormat);
335
	}
336
337
	public function testInGroups() {
338
		$staffmember = $this->objFromFixture('Member', 'staffmember');
339
		$managementmember = $this->objFromFixture('Member', 'managementmember');
0 ignored issues
show
Unused Code introduced by
$managementmember 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...
340
		$accountingmember = $this->objFromFixture('Member', 'accountingmember');
0 ignored issues
show
Unused Code introduced by
$accountingmember 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...
341
		$ceomember = $this->objFromFixture('Member', 'ceomember');
342
343
		$staffgroup = $this->objFromFixture('Group', 'staffgroup');
344
		$managementgroup = $this->objFromFixture('Group', 'managementgroup');
345
		$accountinggroup = $this->objFromFixture('Group', 'accountinggroup');
0 ignored issues
show
Unused Code introduced by
$accountinggroup 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...
346
		$ceogroup = $this->objFromFixture('Group', 'ceogroup');
347
348
		$this->assertTrue(
349
			$staffmember->inGroups(array($staffgroup, $managementgroup)),
350
			'inGroups() succeeds if a membership is detected on one of many passed groups'
351
		);
352
		$this->assertFalse(
353
			$staffmember->inGroups(array($ceogroup, $managementgroup)),
354
			'inGroups() fails if a membership is detected on none of the passed groups'
355
		);
356
		$this->assertFalse(
357
			$ceomember->inGroups(array($staffgroup, $managementgroup), true),
358
			'inGroups() fails if no direct membership is detected on any of the passed groups (in strict mode)'
359
		);
360
	}
361
362
	public function testAddToGroupByCode() {
363
		$grouplessMember = $this->objFromFixture('Member', 'grouplessmember');
364
		$memberlessGroup = $this->objFromFixture('Group','memberlessgroup');
365
366
		$this->assertFalse($grouplessMember->Groups()->exists());
367
		$this->assertFalse($memberlessGroup->Members()->exists());
368
369
		$grouplessMember->addToGroupByCode('memberless');
370
371
		$this->assertEquals($memberlessGroup->Members()->Count(), 1);
372
		$this->assertEquals($grouplessMember->Groups()->Count(), 1);
373
374
		$grouplessMember->addToGroupByCode('somegroupthatwouldneverexist', 'New Group');
375
		$this->assertEquals($grouplessMember->Groups()->Count(), 2);
376
377
		$group = DataObject::get_one('Group', array(
378
			'"Group"."Code"' => 'somegroupthatwouldneverexist'
379
		));
380
		$this->assertNotNull($group);
381
		$this->assertEquals($group->Code, 'somegroupthatwouldneverexist');
382
		$this->assertEquals($group->Title, 'New Group');
383
384
	}
385
386
	public function testRemoveFromGroupByCode() {
387
		$grouplessMember = $this->objFromFixture('Member', 'grouplessmember');
388
		$memberlessGroup = $this->objFromFixture('Group','memberlessgroup');
389
390
		$this->assertFalse($grouplessMember->Groups()->exists());
391
		$this->assertFalse($memberlessGroup->Members()->exists());
392
393
		$grouplessMember->addToGroupByCode('memberless');
394
395
		$this->assertEquals($memberlessGroup->Members()->Count(), 1);
396
		$this->assertEquals($grouplessMember->Groups()->Count(), 1);
397
398
		$grouplessMember->addToGroupByCode('somegroupthatwouldneverexist', 'New Group');
399
		$this->assertEquals($grouplessMember->Groups()->Count(), 2);
400
401
		$group = DataObject::get_one('Group', "\"Code\" = 'somegroupthatwouldneverexist'");
402
		$this->assertNotNull($group);
403
		$this->assertEquals($group->Code, 'somegroupthatwouldneverexist');
404
		$this->assertEquals($group->Title, 'New Group');
405
406
		$grouplessMember->removeFromGroupByCode('memberless');
407
		$this->assertEquals($memberlessGroup->Members()->Count(), 0);
408
		$this->assertEquals($grouplessMember->Groups()->Count(), 1);
409
410
		$grouplessMember->removeFromGroupByCode('somegroupthatwouldneverexist');
411
		$this->assertEquals($grouplessMember->Groups()->Count(), 0);
412
	}
413
414
	public function testInGroup() {
415
		$staffmember = $this->objFromFixture('Member', 'staffmember');
416
		$managementmember = $this->objFromFixture('Member', 'managementmember');
417
		$accountingmember = $this->objFromFixture('Member', 'accountingmember');
418
		$ceomember = $this->objFromFixture('Member', 'ceomember');
419
420
		$staffgroup = $this->objFromFixture('Group', 'staffgroup');
421
		$managementgroup = $this->objFromFixture('Group', 'managementgroup');
422
		$accountinggroup = $this->objFromFixture('Group', 'accountinggroup');
0 ignored issues
show
Unused Code introduced by
$accountinggroup 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...
423
		$ceogroup = $this->objFromFixture('Group', 'ceogroup');
424
425
		$this->assertTrue(
426
			$staffmember->inGroup($staffgroup),
427
			'Direct group membership is detected'
428
		);
429
		$this->assertTrue(
430
			$managementmember->inGroup($staffgroup),
431
			'Users of child group are members of a direct parent group (if not in strict mode)'
432
		);
433
		$this->assertTrue(
434
			$accountingmember->inGroup($staffgroup),
435
			'Users of child group are members of a direct parent group (if not in strict mode)'
436
		);
437
		$this->assertTrue(
438
			$ceomember->inGroup($staffgroup),
439
			'Users of indirect grandchild group are members of a parent group (if not in strict mode)'
440
		);
441
		$this->assertTrue(
442
			$ceomember->inGroup($ceogroup, true),
443
			'Direct group membership is dected (if in strict mode)'
444
		);
445
		$this->assertFalse(
446
			$ceomember->inGroup($staffgroup, true),
447
			'Users of child group are not members of a direct parent group (if in strict mode)'
448
		);
449
		$this->assertFalse(
450
			$staffmember->inGroup($managementgroup),
451
			'Users of parent group are not members of a direct child group'
452
		);
453
		$this->assertFalse(
454
			$staffmember->inGroup($ceogroup),
455
			'Users of parent group are not members of an indirect grandchild group'
456
		);
457
		$this->assertFalse(
458
			$accountingmember->inGroup($managementgroup),
459
			'Users of group are not members of any siblings'
460
		);
461
		$this->assertFalse(
462
			$staffmember->inGroup('does-not-exist'),
463
			'Non-existant group returns false'
464
		);
465
	}
466
467
	/**
468
	 * Tests that the user is able to view their own record, and in turn, they can
469
	 * edit and delete their own record too.
470
	 */
471
	public function testCanManipulateOwnRecord() {
472
		$extensions = $this->removeExtensions(Object::get_extensions('Member'));
473
		$member = $this->objFromFixture('Member', 'test');
474
		$member2 = $this->objFromFixture('Member', 'staffmember');
475
476
		$this->session()->inst_set('loggedInAs', null);
477
478
		/* Not logged in, you can't view, delete or edit the record */
479
		$this->assertFalse($member->canView());
480
		$this->assertFalse($member->canDelete());
481
		$this->assertFalse($member->canEdit());
482
483
		/* Logged in users can edit their own record */
484
		$this->session()->inst_set('loggedInAs', $member->ID);
485
		$this->assertTrue($member->canView());
486
		$this->assertFalse($member->canDelete());
487
		$this->assertTrue($member->canEdit());
488
489
		/* Other uses cannot view, delete or edit others records */
490
		$this->session()->inst_set('loggedInAs', $member2->ID);
491
		$this->assertFalse($member->canView());
492
		$this->assertFalse($member->canDelete());
493
		$this->assertFalse($member->canEdit());
494
495
		$this->addExtensions($extensions);
496
		$this->session()->inst_set('loggedInAs', null);
497
	}
498
499
	public function testAuthorisedMembersCanManipulateOthersRecords() {
500
		$extensions = $this->removeExtensions(Object::get_extensions('Member'));
501
		$member = $this->objFromFixture('Member', 'test');
502
		$member2 = $this->objFromFixture('Member', 'staffmember');
503
504
		/* Group members with SecurityAdmin permissions can manipulate other records */
505
		$this->session()->inst_set('loggedInAs', $member->ID);
506
		$this->assertTrue($member2->canView());
507
		$this->assertTrue($member2->canDelete());
508
		$this->assertTrue($member2->canEdit());
509
510
		$this->addExtensions($extensions);
511
		$this->session()->inst_set('loggedInAs', null);
512
	}
513
514
	public function testExtendedCan() {
515
		$extensions = $this->removeExtensions(Object::get_extensions('Member'));
516
		$member = $this->objFromFixture('Member', 'test');
517
518
		/* Normal behaviour is that you can't view a member unless canView() on an extension returns true */
519
		$this->assertFalse($member->canView());
520
		$this->assertFalse($member->canDelete());
521
		$this->assertFalse($member->canEdit());
522
523
		/* Apply a extension that allows viewing in any case (most likely the case for member profiles) */
524
		Member::add_extension('MemberTest_ViewingAllowedExtension');
525
		$member2 = $this->objFromFixture('Member', 'staffmember');
526
527
		$this->assertTrue($member2->canView());
528
		$this->assertFalse($member2->canDelete());
529
		$this->assertFalse($member2->canEdit());
530
531
		/* Apply a extension that denies viewing of the Member */
532
		Member::remove_extension('MemberTest_ViewingAllowedExtension');
533
		Member::add_extension('MemberTest_ViewingDeniedExtension');
534
		$member3 = $this->objFromFixture('Member', 'managementmember');
535
536
		$this->assertFalse($member3->canView());
537
		$this->assertFalse($member3->canDelete());
538
		$this->assertFalse($member3->canEdit());
539
540
		/* Apply a extension that allows viewing and editing but denies deletion */
541
		Member::remove_extension('MemberTest_ViewingDeniedExtension');
542
		Member::add_extension('MemberTest_EditingAllowedDeletingDeniedExtension');
543
		$member4 = $this->objFromFixture('Member', 'accountingmember');
544
545
		$this->assertTrue($member4->canView());
546
		$this->assertFalse($member4->canDelete());
547
		$this->assertTrue($member4->canEdit());
548
549
		Member::remove_extension('MemberTest_EditingAllowedDeletingDeniedExtension');
550
		$this->addExtensions($extensions);
551
	}
552
553
	/**
554
	 * Tests for {@link Member::getName()} and {@link Member::setName()}
555
	 */
556
	public function testName() {
557
		$member = $this->objFromFixture('Member', 'test');
558
		$member->setName('Test Some User');
559
		$this->assertEquals('Test Some User', $member->getName());
560
		$member->setName('Test');
561
		$this->assertEquals('Test', $member->getName());
562
		$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...
563
		$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...
564
		$this->assertEquals('Test', $member->getName());
565
	}
566
567
	public function testMembersWithSecurityAdminAccessCantEditAdminsUnlessTheyreAdminsThemselves() {
568
		$adminMember = $this->objFromFixture('Member', 'admin');
569
		$otherAdminMember = $this->objFromFixture('Member', 'other-admin');
570
		$securityAdminMember = $this->objFromFixture('Member', 'test');
571
		$ceoMember = $this->objFromFixture('Member', 'ceomember');
572
573
		// Careful: Don't read as english language.
574
		// More precisely this should read canBeEditedBy()
575
576
		$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 568 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...
577
		$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 568 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...
578
		$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 568 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...
579
580
		$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 570 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...
581
		$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 570 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...
582
		$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 570 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...
583
	}
584
585
	public function testOnChangeGroups() {
586
		$staffGroup = $this->objFromFixture('Group', 'staffgroup');
587
		$staffMember = $this->objFromFixture('Member', 'staffmember');
588
		$adminMember = $this->objFromFixture('Member', 'admin');
589
		$newAdminGroup = new Group(array('Title' => 'newadmin'));
590
		$newAdminGroup->write();
591
		Permission::grant($newAdminGroup->ID, 'ADMIN');
592
		$newOtherGroup = new Group(array('Title' => 'othergroup'));
593
		$newOtherGroup->write();
594
595
		$this->assertTrue(
596
			$staffMember->onChangeGroups(array($staffGroup->ID)),
597
			'Adding existing non-admin group relation is allowed for non-admin members'
598
		);
599
		$this->assertTrue(
600
			$staffMember->onChangeGroups(array($newOtherGroup->ID)),
601
			'Adding new non-admin group relation is allowed for non-admin members'
602
		);
603
		$this->assertFalse(
604
			$staffMember->onChangeGroups(array($newAdminGroup->ID)),
605
			'Adding new admin group relation is not allowed for non-admin members'
606
		);
607
608
		$this->session()->inst_set('loggedInAs', $adminMember->ID);
609
		$this->assertTrue(
610
			$staffMember->onChangeGroups(array($newAdminGroup->ID)),
611
			'Adding new admin group relation is allowed for normal users, when granter is logged in as admin'
612
		);
613
		$this->session()->inst_set('loggedInAs', null);
614
615
		$this->assertTrue(
616
			$adminMember->onChangeGroups(array($newAdminGroup->ID)),
617
			'Adding new admin group relation is allowed for admin members'
618
		);
619
	}
620
621
	/**
622
	 * Test Member_GroupSet::add
623
	 */
624
	public function testOnChangeGroupsByAdd() {
625
		$staffMember = $this->objFromFixture('Member', 'staffmember');
626
		$adminMember = $this->objFromFixture('Member', 'admin');
627
628
		// Setup new admin group
629
		$newAdminGroup = new Group(array('Title' => 'newadmin'));
630
		$newAdminGroup->write();
631
		Permission::grant($newAdminGroup->ID, 'ADMIN');
632
633
		// Setup non-admin group
634
		$newOtherGroup = new Group(array('Title' => 'othergroup'));
635
		$newOtherGroup->write();
636
637
		// Test staff can be added to other group
638
		$this->assertFalse($staffMember->inGroup($newOtherGroup));
639
		$staffMember->Groups()->add($newOtherGroup);
640
		$this->assertTrue(
641
			$staffMember->inGroup($newOtherGroup),
642
			'Adding new non-admin group relation is allowed for non-admin members'
643
		);
644
645
		// Test staff member can't be added to admin groups
646
		$this->assertFalse($staffMember->inGroup($newAdminGroup));
647
		$staffMember->Groups()->add($newAdminGroup);
648
		$this->assertFalse(
649
			$staffMember->inGroup($newAdminGroup),
650
			'Adding new admin group relation is not allowed for non-admin members'
651
		);
652
653
		// Test staff member can be added to admin group by admins
654
		$this->logInAs($adminMember);
655
		$staffMember->Groups()->add($newAdminGroup);
656
		$this->assertTrue(
657
			$staffMember->inGroup($newAdminGroup),
658
			'Adding new admin group relation is allowed for normal users, when granter is logged in as admin'
659
		);
660
661
		// Test staff member can be added if they are already admin
662
		$this->session()->inst_set('loggedInAs', null);
663
		$this->assertFalse($adminMember->inGroup($newAdminGroup));
664
		$adminMember->Groups()->add($newAdminGroup);
665
		$this->assertTrue(
666
			$adminMember->inGroup($newAdminGroup),
667
			'Adding new admin group relation is allowed for admin members'
668
		);
669
	}
670
671
	/**
672
	 * Test Member_GroupSet::add
673
	 */
674
	public function testOnChangeGroupsBySetIDList() {
675
		$staffMember = $this->objFromFixture('Member', 'staffmember');
676
677
		// Setup new admin group
678
		$newAdminGroup = new Group(array('Title' => 'newadmin'));
679
		$newAdminGroup->write();
680
		Permission::grant($newAdminGroup->ID, 'ADMIN');
681
682
		// Test staff member can't be added to admin groups
683
		$this->assertFalse($staffMember->inGroup($newAdminGroup));
684
		$staffMember->Groups()->setByIDList(array($newAdminGroup->ID));
685
		$this->assertFalse(
686
			$staffMember->inGroup($newAdminGroup),
687
			'Adding new admin group relation is not allowed for non-admin members'
688
		);
689
	}
690
691
	/**
692
	 * Test that extensions using updateCMSFields() are applied correctly
693
	 */
694
	public function testUpdateCMSFields() {
695
		Member::add_extension('MemberTest_FieldsExtension');
696
697
		$member = singleton('Member');
698
		$fields = $member->getCMSFields();
699
700
		$this->assertNotNull($fields->dataFieldByName('Email'), 'Scaffolded fields are retained');
701
		$this->assertNull($fields->dataFieldByName('Salt'), 'Field modifications run correctly');
702
		$this->assertNotNull($fields->dataFieldByName('TestMemberField'), 'Extension is applied correctly');
703
704
		Member::remove_extension('MemberTest_FieldsExtension');
705
	}
706
707
	/**
708
	 * Test that all members are returned
709
	 */
710
	public function testMap_in_groupsReturnsAll() {
711
		$members = Member::map_in_groups();
712
		$this->assertEquals(13, count($members), 'There are 12 members in the mock plus a fake admin');
713
	}
714
715
	/**
716
	 * Test that only admin members are returned
717
	 */
718
	public function testMap_in_groupsReturnsAdmins() {
719
		$adminID = $this->objFromFixture('Group', 'admingroup')->ID;
720
		$members = Member::map_in_groups($adminID);
721
722
		$admin = $this->objFromFixture('Member', 'admin');
723
		$otherAdmin = $this->objFromFixture('Member', 'other-admin');
724
725
		$this->assertTrue(in_array($admin->getTitle(), $members),
726
			$admin->getTitle().' should be in the returned list.');
727
		$this->assertTrue(in_array($otherAdmin->getTitle(), $members),
728
			$otherAdmin->getTitle().' should be in the returned list.');
729
		$this->assertEquals(2, count($members), 'There should be 2 members from the admin group');
730
	}
731
732
	/**
733
	 * Add the given array of member extensions as class names.
734
	 * This is useful for re-adding extensions after being removed
735
	 * in a test case to produce an unbiased test.
736
	 *
737
	 * @param array $extensions
738
	 * @return array The added extensions
739
	 */
740
	protected function addExtensions($extensions) {
741
		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...
742
			Member::add_extension($extension);
743
		}
744
		return $extensions;
745
	}
746
747
	/**
748
	 * Remove given extensions from Member. This is useful for
749
	 * removing extensions that could produce a biased
750
	 * test result, as some extensions applied by project
751
	 * code or modules can do this.
752
	 *
753
	 * @param array $extensions
754
	 * @return array The removed extensions
755
	 */
756
	protected function removeExtensions($extensions) {
757
		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...
758
			Member::remove_extension($extension);
759
		}
760
		return $extensions;
761
	}
762
763
	public function testGenerateAutologinTokenAndStoreHash() {
764
		$enc = new PasswordEncryptor_Blowfish();
765
766
		$m = new Member();
767
		$m->PasswordEncryption = 'blowfish';
768
		$m->Salt = $enc->salt('123');
769
770
		$token = $m->generateAutologinTokenAndStoreHash();
771
772
		$this->assertEquals($m->encryptWithUserSettings($token), $m->AutoLoginHash, 'Stores the token as ahash.');
773
	}
774
775
	public function testValidateAutoLoginToken() {
776
		$enc = new PasswordEncryptor_Blowfish();
777
778
		$m1 = new Member();
779
		$m1->PasswordEncryption = 'blowfish';
780
		$m1->Salt = $enc->salt('123');
781
		$m1Token = $m1->generateAutologinTokenAndStoreHash();
782
783
		$m2 = new Member();
784
		$m2->PasswordEncryption = 'blowfish';
785
		$m2->Salt = $enc->salt('456');
786
		$m2Token = $m2->generateAutologinTokenAndStoreHash();
0 ignored issues
show
Unused Code introduced by
$m2Token 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...
787
788
		$this->assertTrue($m1->validateAutoLoginToken($m1Token), 'Passes token validity test against matching member.');
789
		$this->assertFalse($m2->validateAutoLoginToken($m1Token), 'Fails token validity test against other member.');
790
	}
791
792
	public function testCanDelete() {
793
		$admin1 = $this->objFromFixture('Member', 'admin');
794
		$admin2 = $this->objFromFixture('Member', 'other-admin');
795
		$member1 = $this->objFromFixture('Member', 'grouplessmember');
796
		$member2 = $this->objFromFixture('Member', 'noformatmember');
797
798
		$this->assertTrue(
799
			$admin1->canDelete($admin2),
0 ignored issues
show
Bug introduced by
It seems like $admin2 defined by $this->objFromFixture('Member', 'other-admin') on line 794 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...
800
			'Admins can delete other admins'
801
		);
802
		$this->assertTrue(
803
			$member1->canDelete($admin2),
0 ignored issues
show
Bug introduced by
It seems like $admin2 defined by $this->objFromFixture('Member', 'other-admin') on line 794 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...
804
			'Admins can delete non-admins'
805
		);
806
		$this->assertFalse(
807
			$admin1->canDelete($admin1),
0 ignored issues
show
Bug introduced by
It seems like $admin1 defined by $this->objFromFixture('Member', 'admin') on line 793 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...
808
			'Admins can not delete themselves'
809
		);
810
		$this->assertFalse(
811
			$member1->canDelete($member2),
0 ignored issues
show
Bug introduced by
It seems like $member2 defined by $this->objFromFixture('Member', 'noformatmember') on line 796 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...
812
			'Non-admins can not delete other non-admins'
813
		);
814
		$this->assertFalse(
815
			$member1->canDelete($member1),
0 ignored issues
show
Bug introduced by
It seems like $member1 defined by $this->objFromFixture('M...er', 'grouplessmember') on line 795 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...
816
			'Non-admins can not delete themselves'
817
		);
818
	}
819
820
	public function testFailedLoginCount() {
821
		$maxFailedLoginsAllowed = 3;
822
		//set up the config variables to enable login lockouts
823
		Config::nest();
824
		Config::inst()->update('Member', 'lock_out_after_incorrect_logins', $maxFailedLoginsAllowed);
825
826
		$member = $this->objFromFixture('Member', 'test');
827
		$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...
828
829
		for ($i = 1; $i < $maxFailedLoginsAllowed; ++$i) {
830
			$member->registerFailedLogin();
831
832
			$this->assertEquals(
833
				++$failedLoginCount,
834
				$member->FailedLoginCount,
835
				'Failed to increment $member->FailedLoginCount'
836
			);
837
838
			$this->assertFalse(
839
				$member->isLockedOut(),
840
				"Member has been locked out too early"
841
			);
842
		}
843
	}
844
845
	public function testMemberValidator()
846
	{
847
		// clear custom requirements for this test
848
		Config::inst()->update('Member_Validator', 'customRequired', null);
849
		$memberA = $this->objFromFixture('Member', 'admin');
850
		$memberB = $this->objFromFixture('Member', 'test');
851
852
		// create a blank form
853
		$form = new MemberTest_ValidatorForm();
854
855
		$validator = new Member_Validator();
856
		$validator->setForm($form);
857
858
		// Simulate creation of a new member via form, but use an existing member identifier
859
		$fail = $validator->php(array(
860
			'FirstName' => 'Test',
861
			'Email' => $memberA->Email
862
		));
863
864
		$this->assertFalse(
865
			$fail,
866
			'Member_Validator must fail when trying to create new Member with existing Email.'
867
		);
868
869
		// populate the form with values from another member
870
		$form->loadDataFrom($memberB);
0 ignored issues
show
Bug introduced by
It seems like $memberB defined by $this->objFromFixture('Member', 'test') on line 850 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...
871
872
		// Assign the validator to an existing member
873
		// (this is basically the same as passing the member ID with the form data)
874
		$validator->setForMember($memberB);
0 ignored issues
show
Documentation introduced by
$memberB is of type object<DataObject>|null, but the function expects a object<Member>.

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...
875
876
		// Simulate update of a member via form and use an existing member Email
877
		$fail = $validator->php(array(
878
			'FirstName' => 'Test',
879
			'Email' => $memberA->Email
880
		));
881
882
		// Simulate update to a new Email address
883
		$pass1 = $validator->php(array(
884
			'FirstName' => 'Test',
885
			'Email' => '[email protected]'
886
		));
887
888
		// Pass in the same Email address that the member already has. Ensure that case is valid
889
		$pass2 = $validator->php(array(
890
			'FirstName' => 'Test',
891
			'Surname' => 'User',
892
			'Email' => $memberB->Email
893
		));
894
895
		$this->assertFalse(
896
			$fail,
897
			'Member_Validator must fail when trying to update existing member with existing Email.'
898
		);
899
900
		$this->assertTrue(
901
			$pass1,
902
			'Member_Validator must pass when Email is updated to a value that\'s not in use.'
903
		);
904
905
		$this->assertTrue(
906
			$pass2,
907
			'Member_Validator must pass when Member updates his own Email to the already existing value.'
908
		);
909
	}
910
911
	public function testMemberValidatorWithExtensions()
912
	{
913
		// clear custom requirements for this test
914
		Config::inst()->update('Member_Validator', 'customRequired', null);
915
916
		// create a blank form
917
		$form = new MemberTest_ValidatorForm();
918
919
		// Test extensions
920
		Member_Validator::add_extension('MemberTest_MemberValidator_SurnameMustMatchFirstNameExtension');
921
		$validator = new Member_Validator();
922
		$validator->setForm($form);
923
924
		// This test should fail, since the extension enforces FirstName == Surname
925
		$fail = $validator->php(array(
926
			'FirstName' => 'Test',
927
			'Surname' => 'User',
928
			'Email' => '[email protected]'
929
		));
930
931
		$pass = $validator->php(array(
932
			'FirstName' => 'Test',
933
			'Surname' => 'Test',
934
			'Email' => '[email protected]'
935
		));
936
937
		$this->assertFalse(
938
			$fail,
939
			'Member_Validator must fail because of added extension.'
940
		);
941
942
		$this->assertTrue(
943
			$pass,
944
			'Member_Validator must succeed, since it meets all requirements.'
945
		);
946
947
		// Add another extension that always fails. This ensures that all extensions are considered in the validation
948
		Member_Validator::add_extension('MemberTest_MemberValidator_AlwaysFailsExtension');
949
		$validator = new Member_Validator();
950
		$validator->setForm($form);
951
952
		// Even though the data is valid, This test should still fail, since one extension always returns false
953
		$fail = $validator->php(array(
954
			'FirstName' => 'Test',
955
			'Surname' => 'Test',
956
			'Email' => '[email protected]'
957
		));
958
959
		$this->assertFalse(
960
			$fail,
961
			'Member_Validator must fail because of added extensions.'
962
		);
963
964
		// Remove added extensions
965
		Member_Validator::remove_extension('MemberTest_MemberValidator_AlwaysFailsExtension');
966
		Member_Validator::remove_extension('MemberTest_MemberValidator_SurnameMustMatchFirstNameExtension');
967
	}
968
969
	public function testCustomMemberValidator()
970
	{
971
		// clear custom requirements for this test
972
		Config::inst()->update('Member_Validator', 'customRequired', null);
973
974
		$member = $this->objFromFixture('Member', 'admin');
975
976
		$form = new MemberTest_ValidatorForm();
977
		$form->loadDataFrom($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by $this->objFromFixture('Member', 'admin') on line 974 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...
978
979
		$validator = new Member_Validator();
980
		$validator->setForm($form);
981
982
		$pass = $validator->php(array(
983
			'FirstName' => 'Borris',
984
			'Email' => '[email protected]'
985
		));
986
987
		$fail = $validator->php(array(
988
			'Email' => '[email protected]',
989
			'Surname' => ''
990
		));
991
992
		$this->assertTrue($pass, 'Validator requires a FirstName and Email');
993
		$this->assertFalse($fail, 'Missing FirstName');
994
995
		$ext = new MemberTest_ValidatorExtension();
996
		$ext->updateValidator($validator);
997
998
		$pass = $validator->php(array(
999
			'FirstName' => 'Borris',
1000
			'Email' => '[email protected]'
1001
		));
1002
1003
		$fail = $validator->php(array(
1004
			'Email' => '[email protected]'
1005
		));
1006
1007
		$this->assertFalse($pass, 'Missing surname');
1008
		$this->assertFalse($fail, 'Missing surname value');
1009
1010
		$fail = $validator->php(array(
1011
			'Email' => '[email protected]',
1012
			'Surname' => 'Silverman'
1013
		));
1014
1015
		$this->assertTrue($fail, 'Passes with email and surname now (no firstname)');
1016
	}
1017
1018
}
1019
1020
/**
1021
 * @package framework
1022
 * @subpackage tests
1023
 */
1024
class MemberTest_ValidatorForm extends Form implements TestOnly {
1025
1026
	public function __construct() {
1027
		parent::__construct(Controller::curr(), __CLASS__, new FieldList(
1028
			new TextField('Email'),
1029
			new TextField('Surname'),
1030
			new TextField('ID'),
1031
			new TextField('FirstName')
1032
		), new FieldList(
1033
			new FormAction('someAction')
1034
		));
1035
	}
1036
}
1037
1038
/**
1039
 * @package framework
1040
 * @subpackage tests
1041
 */
1042
class MemberTest_ValidatorExtension extends DataExtension implements TestOnly {
1043
1044
	public function updateValidator(&$validator) {
1045
		$validator->addRequiredField('Surname');
1046
		$validator->removeRequiredField('FirstName');
1047
	}
1048
}
1049
1050
/**
1051
 * Extension that adds additional validation criteria
1052
 * @package framework
1053
 * @subpackage tests
1054
 */
1055
class MemberTest_MemberValidator_SurnameMustMatchFirstNameExtension extends DataExtension implements TestOnly
1056
{
1057
	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...
1058
		return $data['FirstName'] == $data['Surname'];
1059
	}
1060
}
1061
1062
/**
1063
 * Extension that adds additional validation criteria
1064
 * @package framework
1065
 * @subpackage tests
1066
 */
1067
class MemberTest_MemberValidator_AlwaysFailsExtension extends DataExtension implements TestOnly
1068
{
1069
	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...
1070
		return false;
1071
	}
1072
}
1073
1074
/**
1075
 * @package framework
1076
 * @subpackage tests
1077
 */
1078
class MemberTest_ViewingAllowedExtension extends DataExtension implements TestOnly {
1079
1080
	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...
1081
		return true;
1082
	}
1083
}
1084
1085
/**
1086
 * @package framework
1087
 * @subpackage tests
1088
 */
1089
class MemberTest_ViewingDeniedExtension extends DataExtension implements TestOnly {
1090
1091
	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...
1092
		return false;
1093
	}
1094
}
1095
1096
/**
1097
 * @package framework
1098
 * @subpackage tests
1099
 */
1100
class MemberTest_FieldsExtension extends DataExtension implements TestOnly {
1101
1102
	public function updateCMSFields(FieldList $fields) {
1103
		$fields->addFieldToTab('Root.Main', new TextField('TestMemberField', 'Test'));
1104
	}
1105
1106
}
1107
1108
/**
1109
 * @package framework
1110
 * @subpackage tests
1111
 */
1112
class MemberTest_EditingAllowedDeletingDeniedExtension extends DataExtension implements TestOnly {
1113
1114
	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...
1115
		return true;
1116
	}
1117
1118
	public function canEdit($member = null) {
1119
		return true;
1120
	}
1121
1122
	public function canDelete($member = null) {
1123
		return false;
1124
	}
1125
1126
}
1127
1128
/**
1129
 * @package framework
1130
 * @subpackage tests
1131
 */
1132
class MemberTest_PasswordValidator extends PasswordValidator {
1133
	public function __construct() {
1134
		parent::__construct();
1135
		$this->minLength(7);
1136
		$this->checkHistoricalPasswords(6);
1137
		$this->characterStrength(3, array('lowercase','uppercase','digits','punctuation'));
1138
	}
1139
1140
}
1141