Completed
Push — member-groupset-delete ( a90a9a )
by Loz
11:22
created

MemberTest::testRemoveGroups()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 16
nc 1
nop 0
dl 0
loc 25
rs 8.8571
c 0
b 0
f 0
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
	public function testWriteDoesntMergeNewRecordWithExistingMember() {
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
	public function testWriteDoesntMergeExistingMemberOnIdentifierChange() {
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
		Config::inst()->update('Member', 'notify_password_change', true);
189
190
		$this->clearEmails();
191
192
		$member = $this->objFromFixture('Member', 'test');
193
		$this->assertNotNull($member);
194
		$valid = $member->changePassword('32asDF##$$%%');
195
		$this->assertTrue($valid->valid());
196
197
		$this->assertEmailSent('[email protected]', null, 'Your password has been changed',
198
			'/testuser@example\.com/');
199
200
	}
201
202
	/**
203
	 * Test that triggering "forgotPassword" sends an Email with a reset link
204
	 */
205
	public function testForgotPasswordEmaling() {
206
		$this->clearEmails();
207
		$this->autoFollowRedirection = false;
208
209
		$member = $this->objFromFixture('Member', 'test');
210
		$this->assertNotNull($member);
211
212
		// Initiate a password-reset
213
		$response = $this->post('Security/LostPasswordForm', array('Email' => $member->Email));
214
215
		$this->assertEquals($response->getStatusCode(), 302);
216
217
		// We should get redirected to Security/passwordsent
218
		$this->assertContains('Security/passwordsent/[email protected]',
219
			urldecode($response->getHeader('Location')));
220
221
		// Check existance of reset link
222
		$this->assertEmailSent("[email protected]", null, 'Your password reset link',
223
			'/Security\/changepassword\?m='.$member->ID.'&t=[^"]+/');
224
	}
225
226
	/**
227
	 * Test that passwords validate against NZ e-government guidelines
228
	 *  - don't allow the use of the last 6 passwords
229
	 *  - require at least 3 of lowercase, uppercase, digits and punctuation
230
	 *  - at least 7 characters long
231
	 */
232
	public function testValidatePassword() {
233
		$member = $this->objFromFixture('Member', 'test');
234
		$this->assertNotNull($member);
235
236
		Member::set_password_validator(new MemberTest_PasswordValidator());
237
238
		// BAD PASSWORDS
239
240
		$valid = $member->changePassword('shorty');
241
		$this->assertFalse($valid->valid());
242
		$this->assertContains("TOO_SHORT", $valid->codeList());
243
244
		$valid = $member->changePassword('longone');
245
		$this->assertNotContains("TOO_SHORT", $valid->codeList());
246
		$this->assertContains("LOW_CHARACTER_STRENGTH", $valid->codeList());
247
		$this->assertFalse($valid->valid());
248
249
		$valid = $member->changePassword('w1thNumb3rs');
250
		$this->assertNotContains("LOW_CHARACTER_STRENGTH", $valid->codeList());
251
		$this->assertTrue($valid->valid());
252
253
		// Clear out the MemberPassword table to ensure that the system functions properly in that situation
254
		DB::query("DELETE FROM \"MemberPassword\"");
255
256
		// GOOD PASSWORDS
257
258
		$valid = $member->changePassword('withSym###Ls');
259
		$this->assertNotContains("LOW_CHARACTER_STRENGTH", $valid->codeList());
260
		$this->assertTrue($valid->valid());
261
262
		$valid = $member->changePassword('withSym###Ls2');
263
		$this->assertTrue($valid->valid());
264
265
		$valid = $member->changePassword('withSym###Ls3');
266
		$this->assertTrue($valid->valid());
267
268
		$valid = $member->changePassword('withSym###Ls4');
269
		$this->assertTrue($valid->valid());
270
271
		$valid = $member->changePassword('withSym###Ls5');
272
		$this->assertTrue($valid->valid());
273
274
		$valid = $member->changePassword('withSym###Ls6');
275
		$this->assertTrue($valid->valid());
276
277
		$valid = $member->changePassword('withSym###Ls7');
278
		$this->assertTrue($valid->valid());
279
280
		// CAN'T USE PASSWORDS 2-7, but I can use pasword 1
281
282
		$valid = $member->changePassword('withSym###Ls2');
283
		$this->assertFalse($valid->valid());
284
		$this->assertContains("PREVIOUS_PASSWORD", $valid->codeList());
285
286
		$valid = $member->changePassword('withSym###Ls5');
287
		$this->assertFalse($valid->valid());
288
		$this->assertContains("PREVIOUS_PASSWORD", $valid->codeList());
289
290
		$valid = $member->changePassword('withSym###Ls7');
291
		$this->assertFalse($valid->valid());
292
		$this->assertContains("PREVIOUS_PASSWORD", $valid->codeList());
293
294
		$valid = $member->changePassword('withSym###Ls');
295
		$this->assertTrue($valid->valid());
296
297
		// HAVING DONE THAT, PASSWORD 2 is now available from the list
298
299
		$valid = $member->changePassword('withSym###Ls2');
300
		$this->assertTrue($valid->valid());
301
302
		$valid = $member->changePassword('withSym###Ls3');
303
		$this->assertTrue($valid->valid());
304
305
		$valid = $member->changePassword('withSym###Ls4');
306
		$this->assertTrue($valid->valid());
307
308
		Member::set_password_validator(null);
309
	}
310
311
	/**
312
	 * Test that the PasswordExpiry date is set when passwords are changed
313
	 */
314
	public function testPasswordExpirySetting() {
315
		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...
316
317
		$member = $this->objFromFixture('Member', 'test');
318
		$this->assertNotNull($member);
319
		$valid = $member->changePassword("Xx?1234234");
320
		$this->assertTrue($valid->valid());
321
322
		$expiryDate = date('Y-m-d', time() + 90*86400);
323
		$this->assertEquals($expiryDate, $member->PasswordExpiry);
324
325
		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...
326
		$valid = $member->changePassword("Xx?1234235");
327
		$this->assertTrue($valid->valid());
328
329
		$this->assertNull($member->PasswordExpiry);
330
	}
331
332
	public function testIsPasswordExpired() {
333
		$member = $this->objFromFixture('Member', 'test');
334
		$this->assertNotNull($member);
335
		$this->assertFalse($member->isPasswordExpired());
336
337
		$member = $this->objFromFixture('Member', 'noexpiry');
338
		$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...
339
		$this->assertFalse($member->isPasswordExpired());
340
341
		$member = $this->objFromFixture('Member', 'expiredpassword');
342
		$this->assertTrue($member->isPasswordExpired());
343
344
		// Check the boundary conditions
345
		// If PasswordExpiry == today, then it's expired
346
		$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...
347
		$this->assertTrue($member->isPasswordExpired());
348
349
		// If PasswordExpiry == tomorrow, then it's not
350
		$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...
351
		$this->assertFalse($member->isPasswordExpired());
352
353
	}
354
355
	public function testMemberWithNoDateFormatFallsbackToGlobalLocaleDefaultFormat() {
356
		Config::inst()->update('i18n', 'date_format', 'yyyy-MM-dd');
357
		Config::inst()->update('i18n', 'time_format', 'H:mm');
358
		$member = $this->objFromFixture('Member', 'noformatmember');
359
		$this->assertEquals('yyyy-MM-dd', $member->DateFormat);
360
		$this->assertEquals('H:mm', $member->TimeFormat);
361
	}
362
363
	public function testInGroups() {
364
		$staffmember = $this->objFromFixture('Member', 'staffmember');
365
		$managementmember = $this->objFromFixture('Member', 'managementmember');
366
		$accountingmember = $this->objFromFixture('Member', 'accountingmember');
367
		$ceomember = $this->objFromFixture('Member', 'ceomember');
368
369
		$staffgroup = $this->objFromFixture('Group', 'staffgroup');
370
		$managementgroup = $this->objFromFixture('Group', 'managementgroup');
371
		$accountinggroup = $this->objFromFixture('Group', 'accountinggroup');
372
		$ceogroup = $this->objFromFixture('Group', 'ceogroup');
373
374
		$this->assertTrue(
375
			$staffmember->inGroups(array($staffgroup, $managementgroup)),
376
			'inGroups() succeeds if a membership is detected on one of many passed groups'
377
		);
378
		$this->assertFalse(
379
			$staffmember->inGroups(array($ceogroup, $managementgroup)),
380
			'inGroups() fails if a membership is detected on none of the passed groups'
381
		);
382
		$this->assertFalse(
383
			$ceomember->inGroups(array($staffgroup, $managementgroup), true),
384
			'inGroups() fails if no direct membership is detected on any of the passed groups (in strict mode)'
385
		);
386
	}
387
388
	public function testRemoveGroups()
389
	{
390
		$staffmember = $this->objFromFixture('Member', 'staffmember');
391
392
		$staffgroup = $this->objFromFixture('Group', 'staffgroup');
393
		$managementgroup = $this->objFromFixture('Group', 'managementgroup');
394
395
		$this->assertTrue(
396
			$staffmember->inGroups(array($staffgroup, $managementgroup)),
397
			'inGroups() succeeds if a membership is detected on one of many passed groups'
398
		);
399
400
		$staffmember->Groups()->remove($managementgroup);
401
		$this->assertFalse(
402
			$staffmember->inGroup($managementgroup),
403
			'member was not removed from group using ->Groups()->remove()'
404
		);
405
406
		$staffmember->Groups()->removeAll();
407
		$this->assertEquals(
408
			0,
409
			$staffmember->Groups()->count(),
410
			'member was not removed from all groups using ->Groups()->removeAll()'
411
		);
412
	}
413
414
	public function testAddToGroupByCode() {
415
		$grouplessMember = $this->objFromFixture('Member', 'grouplessmember');
416
		$memberlessGroup = $this->objFromFixture('Group','memberlessgroup');
417
418
		$this->assertFalse($grouplessMember->Groups()->exists());
419
		$this->assertFalse($memberlessGroup->Members()->exists());
420
421
		$grouplessMember->addToGroupByCode('memberless');
422
423
		$this->assertEquals($memberlessGroup->Members()->Count(), 1);
424
		$this->assertEquals($grouplessMember->Groups()->Count(), 1);
425
426
		$grouplessMember->addToGroupByCode('somegroupthatwouldneverexist', 'New Group');
427
		$this->assertEquals($grouplessMember->Groups()->Count(), 2);
428
429
		$group = DataObject::get_one('Group', array(
430
			'"Group"."Code"' => 'somegroupthatwouldneverexist'
431
		));
432
		$this->assertNotNull($group);
433
		$this->assertEquals($group->Code, 'somegroupthatwouldneverexist');
434
		$this->assertEquals($group->Title, 'New Group');
435
436
	}
437
438
	public function testRemoveFromGroupByCode() {
439
		$grouplessMember = $this->objFromFixture('Member', 'grouplessmember');
440
		$memberlessGroup = $this->objFromFixture('Group','memberlessgroup');
441
442
		$this->assertFalse($grouplessMember->Groups()->exists());
443
		$this->assertFalse($memberlessGroup->Members()->exists());
444
445
		$grouplessMember->addToGroupByCode('memberless');
446
447
		$this->assertEquals($memberlessGroup->Members()->Count(), 1);
448
		$this->assertEquals($grouplessMember->Groups()->Count(), 1);
449
450
		$grouplessMember->addToGroupByCode('somegroupthatwouldneverexist', 'New Group');
451
		$this->assertEquals($grouplessMember->Groups()->Count(), 2);
452
453
		$group = DataObject::get_one('Group', "\"Code\" = 'somegroupthatwouldneverexist'");
454
		$this->assertNotNull($group);
455
		$this->assertEquals($group->Code, 'somegroupthatwouldneverexist');
456
		$this->assertEquals($group->Title, 'New Group');
457
458
		$grouplessMember->removeFromGroupByCode('memberless');
459
		$this->assertEquals($memberlessGroup->Members()->Count(), 0);
460
		$this->assertEquals($grouplessMember->Groups()->Count(), 1);
461
462
		$grouplessMember->removeFromGroupByCode('somegroupthatwouldneverexist');
463
		$this->assertEquals($grouplessMember->Groups()->Count(), 0);
464
	}
465
466
	public function testInGroup() {
467
		$staffmember = $this->objFromFixture('Member', 'staffmember');
468
		$managementmember = $this->objFromFixture('Member', 'managementmember');
469
		$accountingmember = $this->objFromFixture('Member', 'accountingmember');
470
		$ceomember = $this->objFromFixture('Member', 'ceomember');
471
472
		$staffgroup = $this->objFromFixture('Group', 'staffgroup');
473
		$managementgroup = $this->objFromFixture('Group', 'managementgroup');
474
		$accountinggroup = $this->objFromFixture('Group', 'accountinggroup');
475
		$ceogroup = $this->objFromFixture('Group', 'ceogroup');
476
477
		$this->assertTrue(
478
			$staffmember->inGroup($staffgroup),
479
			'Direct group membership is detected'
480
		);
481
		$this->assertTrue(
482
			$managementmember->inGroup($staffgroup),
483
			'Users of child group are members of a direct parent group (if not in strict mode)'
484
		);
485
		$this->assertTrue(
486
			$accountingmember->inGroup($staffgroup),
487
			'Users of child group are members of a direct parent group (if not in strict mode)'
488
		);
489
		$this->assertTrue(
490
			$ceomember->inGroup($staffgroup),
491
			'Users of indirect grandchild group are members of a parent group (if not in strict mode)'
492
		);
493
		$this->assertTrue(
494
			$ceomember->inGroup($ceogroup, true),
495
			'Direct group membership is dected (if in strict mode)'
496
		);
497
		$this->assertFalse(
498
			$ceomember->inGroup($staffgroup, true),
499
			'Users of child group are not members of a direct parent group (if in strict mode)'
500
		);
501
		$this->assertFalse(
502
			$staffmember->inGroup($managementgroup),
503
			'Users of parent group are not members of a direct child group'
504
		);
505
		$this->assertFalse(
506
			$staffmember->inGroup($ceogroup),
507
			'Users of parent group are not members of an indirect grandchild group'
508
		);
509
		$this->assertFalse(
510
			$accountingmember->inGroup($managementgroup),
511
			'Users of group are not members of any siblings'
512
		);
513
		$this->assertFalse(
514
			$staffmember->inGroup('does-not-exist'),
515
			'Non-existant group returns false'
516
		);
517
	}
518
519
	/**
520
	 * Tests that the user is able to view their own record, and in turn, they can
521
	 * edit and delete their own record too.
522
	 */
523
	public function testCanManipulateOwnRecord() {
524
		$extensions = $this->removeExtensions(Object::get_extensions('Member'));
525
		$member = $this->objFromFixture('Member', 'test');
526
		$member2 = $this->objFromFixture('Member', 'staffmember');
527
528
		$this->session()->inst_set('loggedInAs', null);
529
530
		/* Not logged in, you can't view, delete or edit the record */
531
		$this->assertFalse($member->canView());
532
		$this->assertFalse($member->canDelete());
533
		$this->assertFalse($member->canEdit());
534
535
		/* Logged in users can edit their own record */
536
		$this->session()->inst_set('loggedInAs', $member->ID);
537
		$this->assertTrue($member->canView());
538
		$this->assertFalse($member->canDelete());
539
		$this->assertTrue($member->canEdit());
540
541
		/* Other uses cannot view, delete or edit others records */
542
		$this->session()->inst_set('loggedInAs', $member2->ID);
543
		$this->assertFalse($member->canView());
544
		$this->assertFalse($member->canDelete());
545
		$this->assertFalse($member->canEdit());
546
547
		$this->addExtensions($extensions);
548
		$this->session()->inst_set('loggedInAs', null);
549
	}
550
551
	public function testAuthorisedMembersCanManipulateOthersRecords() {
552
		$extensions = $this->removeExtensions(Object::get_extensions('Member'));
553
		$member = $this->objFromFixture('Member', 'test');
554
		$member2 = $this->objFromFixture('Member', 'staffmember');
555
556
		/* Group members with SecurityAdmin permissions can manipulate other records */
557
		$this->session()->inst_set('loggedInAs', $member->ID);
558
		$this->assertTrue($member2->canView());
559
		$this->assertTrue($member2->canDelete());
560
		$this->assertTrue($member2->canEdit());
561
562
		$this->addExtensions($extensions);
563
		$this->session()->inst_set('loggedInAs', null);
564
	}
565
566
	public function testExtendedCan() {
567
		$extensions = $this->removeExtensions(Object::get_extensions('Member'));
568
		$member = $this->objFromFixture('Member', 'test');
569
570
		/* Normal behaviour is that you can't view a member unless canView() on an extension returns true */
571
		$this->assertFalse($member->canView());
572
		$this->assertFalse($member->canDelete());
573
		$this->assertFalse($member->canEdit());
574
575
		/* Apply a extension that allows viewing in any case (most likely the case for member profiles) */
576
		Member::add_extension('MemberTest_ViewingAllowedExtension');
577
		$member2 = $this->objFromFixture('Member', 'staffmember');
578
579
		$this->assertTrue($member2->canView());
580
		$this->assertFalse($member2->canDelete());
581
		$this->assertFalse($member2->canEdit());
582
583
		/* Apply a extension that denies viewing of the Member */
584
		Member::remove_extension('MemberTest_ViewingAllowedExtension');
585
		Member::add_extension('MemberTest_ViewingDeniedExtension');
586
		$member3 = $this->objFromFixture('Member', 'managementmember');
587
588
		$this->assertFalse($member3->canView());
589
		$this->assertFalse($member3->canDelete());
590
		$this->assertFalse($member3->canEdit());
591
592
		/* Apply a extension that allows viewing and editing but denies deletion */
593
		Member::remove_extension('MemberTest_ViewingDeniedExtension');
594
		Member::add_extension('MemberTest_EditingAllowedDeletingDeniedExtension');
595
		$member4 = $this->objFromFixture('Member', 'accountingmember');
596
597
		$this->assertTrue($member4->canView());
598
		$this->assertFalse($member4->canDelete());
599
		$this->assertTrue($member4->canEdit());
600
601
		Member::remove_extension('MemberTest_EditingAllowedDeletingDeniedExtension');
602
		$this->addExtensions($extensions);
603
	}
604
605
	/**
606
	 * Tests for {@link Member::getName()} and {@link Member::setName()}
607
	 */
608
	public function testName() {
609
		$member = $this->objFromFixture('Member', 'test');
610
		$member->setName('Test Some User');
611
		$this->assertEquals('Test Some User', $member->getName());
612
		$member->setName('Test');
613
		$this->assertEquals('Test', $member->getName());
614
		$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...
615
		$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...
616
		$this->assertEquals('Test', $member->getName());
617
	}
618
619
	public function testMembersWithSecurityAdminAccessCantEditAdminsUnlessTheyreAdminsThemselves() {
620
		$adminMember = $this->objFromFixture('Member', 'admin');
621
		$otherAdminMember = $this->objFromFixture('Member', 'other-admin');
622
		$securityAdminMember = $this->objFromFixture('Member', 'test');
623
		$ceoMember = $this->objFromFixture('Member', 'ceomember');
624
625
		// Careful: Don't read as english language.
626
		// More precisely this should read canBeEditedBy()
627
628
		$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 620 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($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 620 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
		$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 620 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...
631
632
		$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 622 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...
633
		$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 622 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...
634
		$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 622 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...
635
	}
636
637
	public function testOnChangeGroups() {
638
		$staffGroup = $this->objFromFixture('Group', 'staffgroup');
639
		$staffMember = $this->objFromFixture('Member', 'staffmember');
640
		$adminMember = $this->objFromFixture('Member', 'admin');
641
		$newAdminGroup = new Group(array('Title' => 'newadmin'));
642
		$newAdminGroup->write();
643
		Permission::grant($newAdminGroup->ID, 'ADMIN');
644
		$newOtherGroup = new Group(array('Title' => 'othergroup'));
645
		$newOtherGroup->write();
646
647
		$this->assertTrue(
648
			$staffMember->onChangeGroups(array($staffGroup->ID)),
649
			'Adding existing non-admin group relation is allowed for non-admin members'
650
		);
651
		$this->assertTrue(
652
			$staffMember->onChangeGroups(array($newOtherGroup->ID)),
653
			'Adding new non-admin group relation is allowed for non-admin members'
654
		);
655
		$this->assertFalse(
656
			$staffMember->onChangeGroups(array($newAdminGroup->ID)),
657
			'Adding new admin group relation is not allowed for non-admin members'
658
		);
659
660
		$this->session()->inst_set('loggedInAs', $adminMember->ID);
661
		$this->assertTrue(
662
			$staffMember->onChangeGroups(array($newAdminGroup->ID)),
663
			'Adding new admin group relation is allowed for normal users, when granter is logged in as admin'
664
		);
665
		$this->session()->inst_set('loggedInAs', null);
666
667
		$this->assertTrue(
668
			$adminMember->onChangeGroups(array($newAdminGroup->ID)),
669
			'Adding new admin group relation is allowed for admin members'
670
		);
671
	}
672
673
	/**
674
	 * Test Member_GroupSet::add
675
	 */
676
	public function testOnChangeGroupsByAdd() {
677
		$staffMember = $this->objFromFixture('Member', 'staffmember');
678
		$adminMember = $this->objFromFixture('Member', 'admin');
679
680
		// Setup new admin group
681
		$newAdminGroup = new Group(array('Title' => 'newadmin'));
682
		$newAdminGroup->write();
683
		Permission::grant($newAdminGroup->ID, 'ADMIN');
684
685
		// Setup non-admin group
686
		$newOtherGroup = new Group(array('Title' => 'othergroup'));
687
		$newOtherGroup->write();
688
689
		// Test staff can be added to other group
690
		$this->assertFalse($staffMember->inGroup($newOtherGroup));
691
		$staffMember->Groups()->add($newOtherGroup);
692
		$this->assertTrue(
693
			$staffMember->inGroup($newOtherGroup),
694
			'Adding new non-admin group relation is allowed for non-admin members'
695
		);
696
697
		// Test staff member can't be added to admin groups
698
		$this->assertFalse($staffMember->inGroup($newAdminGroup));
699
		$staffMember->Groups()->add($newAdminGroup);
700
		$this->assertFalse(
701
			$staffMember->inGroup($newAdminGroup),
702
			'Adding new admin group relation is not allowed for non-admin members'
703
		);
704
705
		// Test staff member can be added to admin group by admins
706
		$this->logInAs($adminMember);
707
		$staffMember->Groups()->add($newAdminGroup);
708
		$this->assertTrue(
709
			$staffMember->inGroup($newAdminGroup),
710
			'Adding new admin group relation is allowed for normal users, when granter is logged in as admin'
711
		);
712
713
		// Test staff member can be added if they are already admin
714
		$this->session()->inst_set('loggedInAs', null);
715
		$this->assertFalse($adminMember->inGroup($newAdminGroup));
716
		$adminMember->Groups()->add($newAdminGroup);
717
		$this->assertTrue(
718
			$adminMember->inGroup($newAdminGroup),
719
			'Adding new admin group relation is allowed for admin members'
720
		);
721
	}
722
723
	/**
724
	 * Test Member_GroupSet::add
725
	 */
726
	public function testOnChangeGroupsBySetIDList() {
727
		$staffMember = $this->objFromFixture('Member', 'staffmember');
728
729
		// Setup new admin group
730
		$newAdminGroup = new Group(array('Title' => 'newadmin'));
731
		$newAdminGroup->write();
732
		Permission::grant($newAdminGroup->ID, 'ADMIN');
733
734
		// Test staff member can't be added to admin groups
735
		$this->assertFalse($staffMember->inGroup($newAdminGroup));
736
		$staffMember->Groups()->setByIDList(array($newAdminGroup->ID));
737
		$this->assertFalse(
738
			$staffMember->inGroup($newAdminGroup),
739
			'Adding new admin group relation is not allowed for non-admin members'
740
		);
741
	}
742
743
	/**
744
	 * Test that extensions using updateCMSFields() are applied correctly
745
	 */
746
	public function testUpdateCMSFields() {
747
		Member::add_extension('MemberTest_FieldsExtension');
748
749
		$member = singleton('Member');
750
		$fields = $member->getCMSFields();
751
752
		$this->assertNotNull($fields->dataFieldByName('Email'), 'Scaffolded fields are retained');
753
		$this->assertNull($fields->dataFieldByName('Salt'), 'Field modifications run correctly');
754
		$this->assertNotNull($fields->dataFieldByName('TestMemberField'), 'Extension is applied correctly');
755
756
		Member::remove_extension('MemberTest_FieldsExtension');
757
	}
758
759
	/**
760
	 * Test that all members are returned
761
	 */
762
	public function testMap_in_groupsReturnsAll() {
763
		$members = Member::map_in_groups();
764
		$this->assertEquals(13, count($members), 'There are 12 members in the mock plus a fake admin');
765
	}
766
767
	/**
768
	 * Test that only admin members are returned
769
	 */
770
	public function testMap_in_groupsReturnsAdmins() {
771
		$adminID = $this->objFromFixture('Group', 'admingroup')->ID;
772
		$members = Member::map_in_groups($adminID);
773
774
		$admin = $this->objFromFixture('Member', 'admin');
775
		$otherAdmin = $this->objFromFixture('Member', 'other-admin');
776
777
		$this->assertTrue(in_array($admin->getTitle(), $members),
778
			$admin->getTitle().' should be in the returned list.');
779
		$this->assertTrue(in_array($otherAdmin->getTitle(), $members),
780
			$otherAdmin->getTitle().' should be in the returned list.');
781
		$this->assertEquals(2, count($members), 'There should be 2 members from the admin group');
782
	}
783
784
	/**
785
	 * Add the given array of member extensions as class names.
786
	 * This is useful for re-adding extensions after being removed
787
	 * in a test case to produce an unbiased test.
788
	 *
789
	 * @param array $extensions
790
	 * @return array The added extensions
791
	 */
792
	protected function addExtensions($extensions) {
793
		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...
794
			Member::add_extension($extension);
795
		}
796
		return $extensions;
797
	}
798
799
	/**
800
	 * Remove given extensions from Member. This is useful for
801
	 * removing extensions that could produce a biased
802
	 * test result, as some extensions applied by project
803
	 * code or modules can do this.
804
	 *
805
	 * @param array $extensions
806
	 * @return array The removed extensions
807
	 */
808
	protected function removeExtensions($extensions) {
809
		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...
810
			Member::remove_extension($extension);
811
		}
812
		return $extensions;
813
	}
814
815
	public function testGenerateAutologinTokenAndStoreHash() {
816
		$enc = new PasswordEncryptor_Blowfish();
817
818
		$m = new Member();
819
		$m->PasswordEncryption = 'blowfish';
820
		$m->Salt = $enc->salt('123');
821
822
		$token = $m->generateAutologinTokenAndStoreHash();
823
824
		$this->assertEquals($m->encryptWithUserSettings($token), $m->AutoLoginHash, 'Stores the token as ahash.');
825
	}
826
827
	public function testValidateAutoLoginToken() {
828
		$enc = new PasswordEncryptor_Blowfish();
829
830
		$m1 = new Member();
831
		$m1->PasswordEncryption = 'blowfish';
832
		$m1->Salt = $enc->salt('123');
833
		$m1Token = $m1->generateAutologinTokenAndStoreHash();
834
835
		$m2 = new Member();
836
		$m2->PasswordEncryption = 'blowfish';
837
		$m2->Salt = $enc->salt('456');
838
		$m2Token = $m2->generateAutologinTokenAndStoreHash();
839
840
		$this->assertTrue($m1->validateAutoLoginToken($m1Token), 'Passes token validity test against matching member.');
841
		$this->assertFalse($m2->validateAutoLoginToken($m1Token), 'Fails token validity test against other member.');
842
	}
843
844
	public function testCanDelete() {
845
		$admin1 = $this->objFromFixture('Member', 'admin');
846
		$admin2 = $this->objFromFixture('Member', 'other-admin');
847
		$member1 = $this->objFromFixture('Member', 'grouplessmember');
848
		$member2 = $this->objFromFixture('Member', 'noformatmember');
849
850
		$this->assertTrue(
851
			$admin1->canDelete($admin2),
0 ignored issues
show
Bug introduced by
It seems like $admin2 defined by $this->objFromFixture('Member', 'other-admin') on line 846 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...
852
			'Admins can delete other admins'
853
		);
854
		$this->assertTrue(
855
			$member1->canDelete($admin2),
0 ignored issues
show
Bug introduced by
It seems like $admin2 defined by $this->objFromFixture('Member', 'other-admin') on line 846 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...
856
			'Admins can delete non-admins'
857
		);
858
		$this->assertFalse(
859
			$admin1->canDelete($admin1),
0 ignored issues
show
Bug introduced by
It seems like $admin1 defined by $this->objFromFixture('Member', 'admin') on line 845 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...
860
			'Admins can not delete themselves'
861
		);
862
		$this->assertFalse(
863
			$member1->canDelete($member2),
0 ignored issues
show
Bug introduced by
It seems like $member2 defined by $this->objFromFixture('Member', 'noformatmember') on line 848 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...
864
			'Non-admins can not delete other non-admins'
865
		);
866
		$this->assertFalse(
867
			$member1->canDelete($member1),
0 ignored issues
show
Bug introduced by
It seems like $member1 defined by $this->objFromFixture('M...er', 'grouplessmember') on line 847 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...
868
			'Non-admins can not delete themselves'
869
		);
870
	}
871
872
	public function testFailedLoginCount() {
873
		$maxFailedLoginsAllowed = 3;
874
		//set up the config variables to enable login lockouts
875
		Config::nest();
876
		Config::inst()->update('Member', 'lock_out_after_incorrect_logins', $maxFailedLoginsAllowed);
877
878
		$member = $this->objFromFixture('Member', 'test');
879
		$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...
880
881
		for ($i = 1; $i < $maxFailedLoginsAllowed; ++$i) {
882
			$member->registerFailedLogin();
883
884
			$this->assertEquals(
885
				++$failedLoginCount,
886
				$member->FailedLoginCount,
887
				'Failed to increment $member->FailedLoginCount'
888
			);
889
890
			$this->assertFalse(
891
				$member->isLockedOut(),
892
				"Member has been locked out too early"
893
			);
894
		}
895
	}
896
897
	public function testMemberValidator()
898
	{
899
		// clear custom requirements for this test
900
		Config::inst()->update('Member_Validator', 'customRequired', null);
901
		$memberA = $this->objFromFixture('Member', 'admin');
902
		$memberB = $this->objFromFixture('Member', 'test');
903
904
		// create a blank form
905
		$form = new MemberTest_ValidatorForm();
906
907
		$validator = new Member_Validator();
908
		$validator->setForm($form);
909
910
		// Simulate creation of a new member via form, but use an existing member identifier
911
		$fail = $validator->php(array(
912
			'FirstName' => 'Test',
913
			'Email' => $memberA->Email
914
		));
915
916
		$this->assertFalse(
917
			$fail,
918
			'Member_Validator must fail when trying to create new Member with existing Email.'
919
		);
920
921
		// populate the form with values from another member
922
		$form->loadDataFrom($memberB);
0 ignored issues
show
Bug introduced by
It seems like $memberB defined by $this->objFromFixture('Member', 'test') on line 902 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...
923
924
		// Assign the validator to an existing member
925
		// (this is basically the same as passing the member ID with the form data)
926
		$validator->setForMember($memberB);
927
928
		// Simulate update of a member via form and use an existing member Email
929
		$fail = $validator->php(array(
930
			'FirstName' => 'Test',
931
			'Email' => $memberA->Email
932
		));
933
934
		// Simulate update to a new Email address
935
		$pass1 = $validator->php(array(
936
			'FirstName' => 'Test',
937
			'Email' => '[email protected]'
938
		));
939
940
		// Pass in the same Email address that the member already has. Ensure that case is valid
941
		$pass2 = $validator->php(array(
942
			'FirstName' => 'Test',
943
			'Surname' => 'User',
944
			'Email' => $memberB->Email
945
		));
946
947
		$this->assertFalse(
948
			$fail,
949
			'Member_Validator must fail when trying to update existing member with existing Email.'
950
		);
951
952
		$this->assertTrue(
953
			$pass1,
954
			'Member_Validator must pass when Email is updated to a value that\'s not in use.'
955
		);
956
957
		$this->assertTrue(
958
			$pass2,
959
			'Member_Validator must pass when Member updates his own Email to the already existing value.'
960
		);
961
	}
962
963
	public function testMemberValidatorWithExtensions()
964
	{
965
		// clear custom requirements for this test
966
		Config::inst()->update('Member_Validator', 'customRequired', null);
967
968
		// create a blank form
969
		$form = new MemberTest_ValidatorForm();
970
971
		// Test extensions
972
		Member_Validator::add_extension('MemberTest_MemberValidator_SurnameMustMatchFirstNameExtension');
973
		$validator = new Member_Validator();
974
		$validator->setForm($form);
975
976
		// This test should fail, since the extension enforces FirstName == Surname
977
		$fail = $validator->php(array(
978
			'FirstName' => 'Test',
979
			'Surname' => 'User',
980
			'Email' => '[email protected]'
981
		));
982
983
		$pass = $validator->php(array(
984
			'FirstName' => 'Test',
985
			'Surname' => 'Test',
986
			'Email' => '[email protected]'
987
		));
988
989
		$this->assertFalse(
990
			$fail,
991
			'Member_Validator must fail because of added extension.'
992
		);
993
994
		$this->assertTrue(
995
			$pass,
996
			'Member_Validator must succeed, since it meets all requirements.'
997
		);
998
999
		// Add another extension that always fails. This ensures that all extensions are considered in the validation
1000
		Member_Validator::add_extension('MemberTest_MemberValidator_AlwaysFailsExtension');
1001
		$validator = new Member_Validator();
1002
		$validator->setForm($form);
1003
1004
		// Even though the data is valid, This test should still fail, since one extension always returns false
1005
		$fail = $validator->php(array(
1006
			'FirstName' => 'Test',
1007
			'Surname' => 'Test',
1008
			'Email' => '[email protected]'
1009
		));
1010
1011
		$this->assertFalse(
1012
			$fail,
1013
			'Member_Validator must fail because of added extensions.'
1014
		);
1015
1016
		// Remove added extensions
1017
		Member_Validator::remove_extension('MemberTest_MemberValidator_AlwaysFailsExtension');
1018
		Member_Validator::remove_extension('MemberTest_MemberValidator_SurnameMustMatchFirstNameExtension');
1019
	}
1020
1021
	public function testCustomMemberValidator()
1022
	{
1023
		// clear custom requirements for this test
1024
		Config::inst()->update('Member_Validator', 'customRequired', null);
1025
1026
		$member = $this->objFromFixture('Member', 'admin');
1027
1028
		$form = new MemberTest_ValidatorForm();
1029
		$form->loadDataFrom($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by $this->objFromFixture('Member', 'admin') on line 1026 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...
1030
1031
		$validator = new Member_Validator();
1032
		$validator->setForm($form);
1033
1034
		$pass = $validator->php(array(
1035
			'FirstName' => 'Borris',
1036
			'Email' => '[email protected]'
1037
		));
1038
1039
		$fail = $validator->php(array(
1040
			'Email' => '[email protected]',
1041
			'Surname' => ''
1042
		));
1043
1044
		$this->assertTrue($pass, 'Validator requires a FirstName and Email');
1045
		$this->assertFalse($fail, 'Missing FirstName');
1046
1047
		$ext = new MemberTest_ValidatorExtension();
1048
		$ext->updateValidator($validator);
1049
1050
		$pass = $validator->php(array(
1051
			'FirstName' => 'Borris',
1052
			'Email' => '[email protected]'
1053
		));
1054
1055
		$fail = $validator->php(array(
1056
			'Email' => '[email protected]'
1057
		));
1058
1059
		$this->assertFalse($pass, 'Missing surname');
1060
		$this->assertFalse($fail, 'Missing surname value');
1061
1062
		$fail = $validator->php(array(
1063
			'Email' => '[email protected]',
1064
			'Surname' => 'Silverman'
1065
		));
1066
1067
		$this->assertTrue($fail, 'Passes with email and surname now (no firstname)');
1068
	}
1069
1070
	public function testCurrentUser() {
1071
		$this->assertNull(Member::currentUser());
1072
1073
		$adminMember = $this->objFromFixture('Member', 'admin');
1074
		$this->logInAs($adminMember);
1075
1076
		$userFromSession = Member::currentUser();
1077
		$this->assertEquals($adminMember->ID, $userFromSession->ID);
1078
	}
1079
1080
}
1081
1082
/**
1083
 * @package framework
1084
 * @subpackage tests
1085
 */
1086
class MemberTest_ValidatorForm extends Form implements TestOnly {
1087
1088
	public function __construct() {
1089
		parent::__construct(Controller::curr(), __CLASS__, new FieldList(
1090
			new TextField('Email'),
1091
			new TextField('Surname'),
1092
			new TextField('ID'),
1093
			new TextField('FirstName')
1094
		), new FieldList(
1095
			new FormAction('someAction')
1096
		));
1097
	}
1098
}
1099
1100
/**
1101
 * @package framework
1102
 * @subpackage tests
1103
 */
1104
class MemberTest_ValidatorExtension extends DataExtension implements TestOnly {
1105
1106
	public function updateValidator(&$validator) {
1107
		$validator->addRequiredField('Surname');
1108
		$validator->removeRequiredField('FirstName');
1109
	}
1110
}
1111
1112
/**
1113
 * Extension that adds additional validation criteria
1114
 * @package framework
1115
 * @subpackage tests
1116
 */
1117
class MemberTest_MemberValidator_SurnameMustMatchFirstNameExtension extends DataExtension implements TestOnly
1118
{
1119
	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...
1120
		return $data['FirstName'] == $data['Surname'];
1121
	}
1122
}
1123
1124
/**
1125
 * Extension that adds additional validation criteria
1126
 * @package framework
1127
 * @subpackage tests
1128
 */
1129
class MemberTest_MemberValidator_AlwaysFailsExtension extends DataExtension implements TestOnly
1130
{
1131
	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...
1132
		return false;
1133
	}
1134
}
1135
1136
/**
1137
 * @package framework
1138
 * @subpackage tests
1139
 */
1140
class MemberTest_ViewingAllowedExtension extends DataExtension implements TestOnly {
1141
1142
	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...
1143
		return true;
1144
	}
1145
}
1146
1147
/**
1148
 * @package framework
1149
 * @subpackage tests
1150
 */
1151
class MemberTest_ViewingDeniedExtension extends DataExtension implements TestOnly {
1152
1153
	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...
1154
		return false;
1155
	}
1156
}
1157
1158
/**
1159
 * @package framework
1160
 * @subpackage tests
1161
 */
1162
class MemberTest_FieldsExtension extends DataExtension implements TestOnly {
1163
1164
	public function updateCMSFields(FieldList $fields) {
1165
		$fields->addFieldToTab('Root.Main', new TextField('TestMemberField', 'Test'));
1166
	}
1167
1168
}
1169
1170
/**
1171
 * @package framework
1172
 * @subpackage tests
1173
 */
1174
class MemberTest_EditingAllowedDeletingDeniedExtension extends DataExtension implements TestOnly {
1175
1176
	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...
1177
		return true;
1178
	}
1179
1180
	public function canEdit($member = null) {
1181
		return true;
1182
	}
1183
1184
	public function canDelete($member = null) {
1185
		return false;
1186
	}
1187
1188
}
1189
1190
/**
1191
 * @package framework
1192
 * @subpackage tests
1193
 */
1194
class MemberTest_PasswordValidator extends PasswordValidator {
1195
	public function __construct() {
1196
		parent::__construct();
1197
		$this->minLength(7);
1198
		$this->checkHistoricalPasswords(6);
1199
		$this->characterStrength(3, array('lowercase','uppercase','digits','punctuation'));
1200
	}
1201
1202
}
1203