ElggUser   D
last analyzed

Complexity

Total Complexity 59

Size/Duplication

Total Lines 454
Duplicated Lines 0 %

Test Coverage

Coverage 84.02%

Importance

Changes 2
Bugs 2 Features 0
Metric Value
eloc 154
c 2
b 2
f 0
dl 0
loc 454
ccs 142
cts 169
cp 0.8402
rs 4.08
wmc 59

23 Methods

Rating   Name   Duplication   Size   Complexity  
A unban() 0 16 3
A isBanned() 0 2 1
A setValidationStatus() 0 24 4
A getObjects() 0 5 1
A setLastAction() 0 16 2
A initializeAttributes() 0 15 1
A getType() 0 2 1
A removeAdmin() 0 15 3
A isAdmin() 0 2 1
C __set() 0 35 12
A makeAdmin() 0 15 3
A getLanguage() 0 10 3
A ban() 0 16 3
A isValidated() 0 6 2
A prepareObject() 0 7 1
A getNotificationSettings() 0 18 5
A setLastLogin() 0 12 2
A setPassword() 0 6 2
A getOwnerGUID() 0 7 2
A getGroups() 0 6 1
A getPluginSetting() 0 9 2
A setNotificationSetting() 0 7 2
A persistentDelete() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like ElggUser 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.

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 ElggUser, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
use Elgg\Exceptions\InvalidArgumentException as ElggInvalidArgumentException;
4
use Elgg\Exceptions\Configuration\RegistrationException;
5
use Elgg\Traits\Entity\Friends;
6
use Elgg\Traits\Entity\PluginSettings;
7
use Elgg\Traits\Entity\ProfileData;
8
9
/**
10
 * A user entity
11
 *
12
 * @property      string $name             The display name that the user will be known by in the network
13
 * @property      string $username         The short, reference name for the user in the network
14
 * @property      string $email            The email address to which Elgg will send email notifications
15
 * @property      string $language         The language preference of the user (ISO 639-1 formatted)
16
 * @property-read string $banned           'yes' if the user is banned from the network, 'no' otherwise
17
 * @property      string $ban_reason       The reason why the user was banned
18
 * @property-read string $admin            'yes' if the user is an administrator of the network, 'no' otherwise
19
 * @property      bool   $validated        User validation status
20
 * @property      string $validated_method User validation method
21
 * @property      int    $validated_ts     A UNIX timestamp of the last moment a users validation status is set to true
22
 * @property-read string $password_hash    The hashed password of the user
23
 * @property-read int    $prev_last_action A UNIX timestamp of the previous last action
24
 * @property-read int    $first_login      A UNIX timestamp of the first login
25
 * @property-read int    $last_login       A UNIX timestamp of the last login
26
 * @property-read int    $prev_last_login  A UNIX timestamp of the previous login
27
 */
28
class ElggUser extends \ElggEntity {
29
30
	use Friends;
31
	use PluginSettings {
32
		getPluginSetting as protected psGetPluginSetting;
33
	}
34
	use ProfileData;
35
	
36
	/**
37
	 * {@inheritdoc}
38
	 */
39 1753
	protected function initializeAttributes() {
40 1753
		parent::initializeAttributes();
41 1753
		$this->attributes['subtype'] = 'user';
42
		
43 1753
		$this->attributes['access_id'] = ACCESS_PUBLIC;
44 1753
		$this->attributes['owner_guid'] = 0; // Users aren't owned by anyone, even if they are admin created.
45 1753
		$this->attributes['container_guid'] = 0; // Users aren't contained by anyone, even if they are admin created.
46
		
47
		// Before Elgg 3.0 this was handled by database logic
48 1753
		$this->setMetadata('banned', 'no');
49 1753
		$this->setMetadata('admin', 'no');
50 1753
		$this->language = elgg_get_config('language');
51 1753
		$this->prev_last_action = 0;
52 1753
		$this->last_login = 0;
53 1753
		$this->prev_last_login = 0;
54
	}
55
56
	/**
57
	 * {@inheritdoc}
58
	 */
59 1753
	public function getType(): string {
60 1753
		return 'user';
61
	}
62
	
63
	/**
64
	 * Get user language or default to site language
65
	 *
66
	 * @param string $fallback If this is provided, it will be returned if the user doesn't have a language set.
67
	 *                         If null, the site language will be returned.
68
	 *
69
	 * @return string
70
	 */
71 53
	public function getLanguage(string $fallback = null): string {
72 53
		if (!empty($this->language)) {
73 48
			return $this->language;
74
		}
75
		
76 5
		if ($fallback !== null) {
77
			return $fallback;
78
		}
79
		
80 5
		return elgg_get_config('language');
81
	}
82
83
	/**
84
	 * {@inheritdoc}
85
	 *
86
	 * @throws \Elgg\Exceptions\InvalidArgumentException
87
	 */
88 1753
	public function __set($name, $value) {
89
		switch ($name) {
90 1753
			case 'salt':
91 1753
			case 'password':
92
				_elgg_services()->logger->error("User entities no longer contain {$name}");
93
				return;
94 1753
			case 'password_hash':
95
				_elgg_services()->logger->error('password_hash is a readonly attribute.');
96
				return;
97 1753
			case 'email':
98
				try {
99 1437
					_elgg_services()->accounts->assertValidEmail($value);
100
				} catch (RegistrationException $ex) {
101
					throw new ElggInvalidArgumentException($ex->getMessage(), $ex->getCode(), $ex);
102
				}
103 1437
				break;
104 1753
			case 'username':
105
				try {
106 1448
					_elgg_services()->accounts->assertValidUsername($value);
107
				} catch (RegistrationException $ex) {
108
					throw new ElggInvalidArgumentException($ex->getMessage(), $ex->getCode(), $ex);
109
				}
110
				
111 1448
				$existing_user = elgg_get_user_by_username($value);
112 1448
				if ($existing_user instanceof \ElggUser && ($existing_user->guid !== $this->guid)) {
113
					throw new ElggInvalidArgumentException("{$name} is supposed to be unique for ElggUser");
114
				}
115 1448
				break;
116 1753
			case 'admin':
117 1
				throw new ElggInvalidArgumentException(_elgg_services()->translator->translate('ElggUser:Error:SetAdmin', ['makeAdmin() / removeAdmin()']));
118 1753
			case 'banned':
119 1
				throw new ElggInvalidArgumentException(_elgg_services()->translator->translate('ElggUser:Error:SetBanned', ['ban() / unban()']));
120
		}
121
		
122 1753
		parent::__set($name, $value);
123
	}
124
	
125
	/**
126
	 * Ban this user.
127
	 *
128
	 * @param string $reason Optional reason
129
	 *
130
	 * @return bool
131
	 */
132 1260
	public function ban(string $reason = ''): bool {
133
134 1260
		if (!$this->canEdit()) {
135 1
			return false;
136
		}
137
		
138 1259
		if (!_elgg_services()->events->trigger('ban', 'user', $this)) {
139
			return false;
140
		}
141
142 1259
		$this->ban_reason = $reason;
143 1259
		$this->setMetadata('banned', 'yes');
144
145 1259
		$this->invalidateCache();
146
147 1259
		return true;
148
	}
149
150
	/**
151
	 * Unban this user.
152
	 *
153
	 * @return bool
154
	 */
155 2
	public function unban(): bool {
156
		
157 2
		if (!$this->canEdit()) {
158
			return false;
159
		}
160
161 2
		if (!_elgg_services()->events->trigger('unban', 'user', $this)) {
162
			return false;
163
		}
164
165 2
		unset($this->ban_reason);
166 2
		$this->setMetadata('banned', 'no');
167
168 2
		$this->invalidateCache();
169
170 2
		return true;
171
	}
172
173
	/**
174
	 * Is this user banned or not?
175
	 *
176
	 * @return bool
177
	 */
178 85
	public function isBanned(): bool {
179 85
		return $this->banned === 'yes';
180
	}
181
182
	/**
183
	 * Is this user admin?
184
	 *
185
	 * @return bool
186
	 */
187 745
	public function isAdmin(): bool {
188 745
		return $this->admin === 'yes';
189
	}
190
191
	/**
192
	 * Make the user an admin
193
	 *
194
	 * @return bool
195
	 */
196 35
	public function makeAdmin(): bool {
197
		
198 35
		if ($this->isAdmin()) {
199
			return true;
200
		}
201
202 35
		if (!_elgg_services()->events->trigger('make_admin', 'user', $this)) {
203
			return false;
204
		}
205
206 35
		$this->setMetadata('admin', 'yes');
207
208 35
		$this->invalidateCache();
209
		
210 35
		return true;
211
	}
212
213
	/**
214
	 * Remove the admin flag for user
215
	 *
216
	 * @return bool
217
	 */
218 4
	public function removeAdmin(): bool {
219
220 4
		if (!$this->isAdmin()) {
221 2
			return true;
222
		}
223
224 2
		if (!_elgg_services()->events->trigger('remove_admin', 'user', $this)) {
225
			return false;
226
		}
227
228 2
		$this->setMetadata('admin', 'no');
229
230 2
		$this->invalidateCache();
231
		
232 2
		return true;
233
	}
234
	
235
	/**
236
	 * Sets the last logon time of the user to right now.
237
	 *
238
	 * @return void
239
	 */
240 12
	public function setLastLogin(): void {
241 12
		$time = $this->getCurrentTime()->getTimestamp();
242
		
243 12
		if ($this->last_login == $time) {
244
			// no change required
245 1
			return;
246
		}
247
		
248 12
		elgg_call(ELGG_IGNORE_ACCESS | ELGG_DISABLE_SYSTEM_LOG, function() use ($time) {
249
			// these writes actually work, we just type hint read-only.
250 12
			$this->prev_last_login = $this->last_login;
251 12
			$this->last_login = $time;
252 12
		});
253
	}
254
	
255
	/**
256
	 * Sets the last action time of the given user to right now.
257
	 *
258
	 * @return void
259
	 */
260 1
	public function setLastAction(): void {
261
		
262 1
		$time = $this->getCurrentTime()->getTimestamp();
263
		
264 1
		if ($this->last_action == $time) {
265
			// no change required
266 1
			return;
267
		}
268
		
269
		$user = $this;
270
		
271
		elgg_register_event_handler('shutdown', 'system', function () use ($user, $time) {
272
			// these writes actually work, we just type hint read-only.
273
			$user->prev_last_action = $user->last_action;
274
		
275
			$user->updateLastAction($time);
276
		});
277
	}
278
	
279
	/**
280
	 * Gets the validation status of a user.
281
	 *
282
	 * @return bool|null Null means status was not set for this user.
283
	 */
284 1261
	public function isValidated(): ?bool {
285 1261
		if (!isset($this->validated)) {
286 1261
			return null;
287
		}
288
		
289 1254
		return (bool) $this->validated;
290
	}
291
	
292
	/**
293
	 * Set the validation status for a user.
294
	 *
295
	 * @param bool   $status Validated (true) or unvalidated (false)
296
	 * @param string $method Optional method to say how a user was validated
297
	 *
298
	 * @return void
299
	 */
300 1258
	public function setValidationStatus(bool $status, string $method = ''): void {
301 1258
		if ($status === $this->isValidated()) {
302
			// no change needed
303 1250
			return;
304
		}
305
		
306 1258
		$this->validated = $status;
307
		
308 1258
		if ($status) {
309 1255
			$this->validated_method = $method;
310 1255
			$this->validated_ts = time();
311
		
312
			// make sure the user is enabled
313 1255
			if (!$this->isEnabled()) {
314 1
				$this->enable();
315
			}
316
			
317
			// let the system know the user is validated
318 1255
			_elgg_services()->events->triggerAfter('validate', 'user', $this);
319
		} else {
320
			// invalidating
321 7
			unset($this->validated_ts);
322 7
			unset($this->validated_method);
323 7
			_elgg_services()->events->triggerAfter('invalidate', 'user', $this);
324
		}
325
	}
326
327
	/**
328
	 * Gets the user's groups
329
	 *
330
	 * @param array $options Options array.
331
	 *
332
	 * @return \ElggGroup[]|int|mixed
333
	 */
334 2
	public function getGroups(array $options = []) {
335 2
		$options['type'] = 'group';
336 2
		$options['relationship'] = 'member';
337 2
		$options['relationship_guid'] = $this->guid;
338
339 2
		return elgg_get_entities($options);
340
	}
341
342
	/**
343
	 * {@inheritdoc}
344
	 */
345
	public function getObjects(array $options = []) {
346
		$options['type'] = 'object';
347
		$options['owner_guid'] = $this->guid;
348
349
		return elgg_get_entities($options);
350
	}
351
352
	/**
353
	 * Get a user's owner GUID
354
	 *
355
	 * Returns its own GUID if the user is not owned.
356
	 *
357
	 * @return int
358
	 */
359 54
	public function getOwnerGUID(): int {
360 54
		$owner_guid = parent::getOwnerGUID();
361 54
		if ($owner_guid === 0) {
362 54
			$owner_guid = (int) $this->guid;
363
		}
364
365 54
		return $owner_guid;
366
	}
367
368
	/**
369
	 * {@inheritdoc}
370
	 */
371 54
	protected function prepareObject(\Elgg\Export\Entity $object) {
372 54
		$object = parent::prepareObject($object);
373 54
		$object->name = $this->getDisplayName();
374 54
		$object->username = $this->username;
375 54
		$object->language = $this->language;
376 54
		unset($object->read_access);
377 54
		return $object;
378
	}
379
380
	/**
381
	 * Set the necessary metadata to store a hash of the user's password.
382
	 *
383
	 * @param string $password The password to be hashed
384
	 *
385
	 * @return void
386
	 * @since 1.10.0
387
	 */
388 1258
	public function setPassword(string $password): void {
389 1258
		$this->setMetadata('password_hash', _elgg_services()->passwords->generateHash($password));
390 1258
		if ($this->guid === elgg_get_logged_in_user_guid()) {
391
			// update the session user token, so this session remains valid
392
			// other sessions for this user will be invalidated
393 2
			_elgg_services()->session_manager->setUserToken();
394
		}
395
	}
396
397
	/**
398
	 * Enable or disable a notification delivery method
399
	 *
400
	 * @param string $method  Method name
401
	 * @param bool   $enabled Enabled or disabled (default: true)
402
	 * @param string $purpose For what purpose is the notification setting used (default: 'default')
403
	 *
404
	 * @return bool
405
	 * @throws \Elgg\Exceptions\InvalidArgumentException
406
	 */
407 1271
	public function setNotificationSetting(string $method, bool $enabled = true, string $purpose = 'default'): bool {
408 1271
		if (empty($purpose)) {
409
			throw new ElggInvalidArgumentException(__METHOD__ . ' requires $purpose to be set to a non-empty string');
410
		}
411
		
412 1271
		$this->{"notification:{$purpose}:{$method}"} = (int) $enabled;
413 1271
		return $this->save();
414
	}
415
416
	/**
417
	 * Returns users's notification settings
418
	 * <code>
419
	 *    [
420
	 *       'email' => true, // enabled
421
	 *       'ajax' => false, // disabled
422
	 *    ]
423
	 * </code>
424
	 *
425
	 * @param string $purpose For what purpose to get the notification settings (default: 'default')
426
	 *
427
	 * @return array
428
	 * @throws \Elgg\Exceptions\InvalidArgumentException
429
	 */
430 336
	public function getNotificationSettings(string $purpose = 'default'): array {
431 336
		if (empty($purpose)) {
432
			throw new ElggInvalidArgumentException(__METHOD__ . ' requires $purpose to be set to a non-empty string');
433
		}
434
		
435 336
		$settings = [];
436
437 336
		$methods = _elgg_services()->notifications->getMethods();
438 336
		foreach ($methods as $method) {
439 336
			if ($purpose !== 'default' && !isset($this->{"notification:{$purpose}:{$method}"})) {
440
				// fallback to the default settings
441 309
				$settings[$method] = (bool) $this->{"notification:default:{$method}"};
442
			} else {
443 35
				$settings[$method] = (bool) $this->{"notification:{$purpose}:{$method}"};
444
			}
445
		}
446
447 336
		return $settings;
448
	}
449
	
450
	/**
451
	 * {@inheritdoc}
452
	 */
453 1252
	public function persistentDelete(bool $recursive = true): bool {
454 1252
		$result = parent::persistentDelete($recursive);
455 1252
		if ($result) {
456
			// cleanup remember me cookie records
457 1251
			_elgg_services()->users_remember_me_cookies_table->deleteAllHashes($this);
458
		}
459
		
460 1252
		return $result;
461
	}
462
	
463
	/**
464
	 * Get a plugin setting
465
	 *
466
	 * @param string $plugin_id plugin ID
467
	 * @param string $name      setting name
468
	 * @param mixed  $default   default setting value
469
	 *
470
	 * @return mixed
471
	 * @see \Elgg\Traits\Entity\PluginSettings::getPluginSetting()
472
	 */
473 14
	public function getPluginSetting(string $plugin_id, string $name, $default = null) {
474 14
		$plugin = _elgg_services()->plugins->get($plugin_id);
475 14
		if ($plugin instanceof \ElggPlugin) {
476 6
			$static_defaults = (array) $plugin->getStaticConfig('user_settings', []);
477
			
478 6
			$default = elgg_extract($name, $static_defaults, $default);
479
		}
480
		
481 14
		return $this->psGetPluginSetting($plugin_id, $name, $default);
482
	}
483
}
484