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