Passed
Push — master ( f13f78...5c1b24 )
by Ismayil
04:22
created

engine/classes/Elgg/Database/UsersTable.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Elgg\Database;
4
5
use Elgg\Cache\EntityCache;
6
use Elgg\Config as Conf;
7
use Elgg\Database;
8
use Elgg\Database\EntityTable;
9
use Elgg\EventsService;
10
use ElggUser;
11
use RegistrationException;
12
13
/**
14
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
15
 *
16
 * @access private
17
 *
18
 * @package    Elgg.Core
19
 * @subpackage Database
20
 * @since      1.10.0
21
 */
22
class UsersTable {
23
24
	use \Elgg\TimeUsing;
25
26
	/**
27
	 * @var Conf
28
	 */
29
	protected $config;
30
31
	/**
32
	 * @var Database
33
	 */
34
	protected $db;
35
36
	/**
37
	 * @var EntityTable
38
	 */
39
	protected $entities;
40
41
	/**
42
	 * @var EntityCache
43
	 */
44
	protected $entity_cache;
45
46
	/**
47
	 * @var EventsService
48
	 */
49
	protected $events;
50
51
	/**
52
	 * @var string
53
	 */
54
	protected $table;
55
56
	/**
57
	 * Constructor
58
	 *
59
	 * @param Conf          $config   Config
60
	 * @param Database      $db       Database
61
	 * @param EntityTable   $entities Entity table
62
	 * @param EntityCache   $cache    Entity cache
63
	 * @param EventsService $events   Event service
64
	 */
65 197 View Code Duplication
	public function __construct(
66
	Conf $config, Database $db, EntityTable $entities, EntityCache $cache, EventsService $events
67
	) {
68 197
		$this->config = $config;
69 197
		$this->db = $db;
70 197
		$this->table = $this->db->prefix . "users_entity";
71 197
		$this->entities = $entities;
72 197
		$this->entity_cache = $cache;
73 197
		$this->events = $events;
74 197
	}
75
76
	/**
77
	 * Return the user specific details of a user by a row.
78
	 *
79
	 * @param int $guid The \ElggUser guid
80
	 *
81
	 * @return mixed
82
	 * @access private
83
	 */
84
	public function getRow($guid) {
85
		$sql = "
86
			SELECT * FROM {$this->table}
87
			WHERE guid = :guid
88
		";
89
		$params = [
90
			':guid' => $guid,
91
		];
92
		return $this->db->getDataRow($sql, null, $params);
93
	}
94
95
	/**
96
	 * Disables all of a user's entities
97
	 *
98
	 * @param int $owner_guid The owner GUID
99
	 * @return bool Depending on success
100
	 * @deprecated 2.3
101
	 */
102
	public function disableEntities($owner_guid) {
103
		return $this->entities->disableEntities($owner_guid);
104
	}
105
106
	/**
107
	 * Ban a user (calls events, stores the reason)
108
	 *
109
	 * @param int    $user_guid The user guid
110
	 * @param string $reason    A reason
111
	 * @return bool
112
	 */
113
	public function ban($user_guid, $reason = "") {
114
115
		$user = get_entity($user_guid);
116
117
		if (!$user instanceof ElggUser || !$user->canEdit()) {
118
			return false;
119
		}
120
121
		if (!$this->events->trigger('ban', 'user', $user)) {
122
			return false;
123
		}
124
125
		$user->ban_reason = $reason;
126
127
		_elgg_invalidate_cache_for_entity($user_guid);
128
		_elgg_invalidate_memcache_for_entity($user_guid);
129
130
		if ($this->markBanned($user_guid, true)) {
131
			return true;
132
		}
133
134
		return false;
135
	}
136
137
	/**
138
	 * Mark a user entity banned or unbanned.
139
	 *
140
	 * @note Use ban() or unban()
141
	 *
142
	 * @param int  $guid   User GUID
143
	 * @param bool $banned Mark the user banned?
144
	 * @return int Num rows affected
145
	 */
146
	public function markBanned($guid, $banned) {
147
148
		$query = "
149
			UPDATE {$this->table}
150
			SET banned = :banned
151
			WHERE guid = :guid
152
		";
153
154
		$params = [
155
			':banned' => $banned ? 'yes' : 'no',
156
			':guid' => (int) $guid,
157
		];
158
159
		return $this->db->updateData($query, true, $params);
160
	}
161
162
	/**
163
	 * Unban a user (calls events, removes the reason)
164
	 *
165
	 * @param int $user_guid Unban a user
166
	 * @return bool
167
	 */
168
	public function unban($user_guid) {
169
170
		$user = get_entity($user_guid);
171
172
		if (!$user instanceof ElggUser || !$user->canEdit()) {
173
			return false;
174
		}
175
176
		if (!$this->events->trigger('unban', 'user', $user)) {
177
			return false;
178
		}
179
180
		$user->deleteMetadata('ban_reason');
181
182
		_elgg_invalidate_cache_for_entity($user_guid);
183
		_elgg_invalidate_memcache_for_entity($user_guid);
184
185
		return $this->markBanned($user_guid, false);
186
	}
187
188
	/**
189
	 * Makes user $guid an admin.
190
	 *
191
	 * @param int $user_guid User guid
192
	 * @return bool
193
	 */
194 View Code Duplication
	public function makeAdmin($user_guid) {
195
		$user = get_entity($user_guid);
196
197
		if (!$user instanceof ElggUser || !$user->canEdit()) {
198
			return false;
199
		}
200
201
		if (!$this->events->trigger('make_admin', 'user', $user)) {
202
			return false;
203
		}
204
205
		$query = "
206
			UPDATE {$this->table}
207
			SET admin = 'yes'
208
			WHERE guid = :guid
209
		";
210
211
		$params = [
212
			':guid' => (int) $user_guid,
213
		];
214
215
		_elgg_invalidate_cache_for_entity($user_guid);
216
		_elgg_invalidate_memcache_for_entity($user_guid);
217
218
		if ($this->db->updateData($query, true, $params)) {
219
			return true;
220
		}
221
222
		return false;
223
	}
224
225
	/**
226
	 * Removes user $guid's admin flag.
227
	 *
228
	 * @param int $user_guid User GUID
229
	 * @return bool
230
	 */
231 View Code Duplication
	public function removeAdmin($user_guid) {
232
233
		$user = get_entity($user_guid);
234
235
		if (!$user instanceof ElggUser || !$user->canEdit()) {
236
			return false;
237
		}
238
239
		if (!$this->events->trigger('remove_admin', 'user', $user)) {
240
			return false;
241
		}
242
243
		$query = "
244
			UPDATE {$this->table}
245
			SET admin = 'no'
246
			WHERE guid = :guid
247
		";
248
249
		$params = [
250
			':guid' => (int) $user_guid,
251
		];
252
253
		_elgg_invalidate_cache_for_entity($user_guid);
254
		_elgg_invalidate_memcache_for_entity($user_guid);
255
256
		if ($this->db->updateData($query, true, $params)) {
257
			return true;
258
		}
259
260
		return false;
261
	}
262
263
	/**
264
	 * Get user by username
265
	 *
266
	 * @param string $username The user's username
267
	 *
268
	 * @return ElggUser|false Depending on success
269
	 */
270
	public function getByUsername($username) {
271
272
		// Fixes #6052. Username is frequently sniffed from the path info, which,
273
		// unlike $_GET, is not URL decoded. If the username was not URL encoded,
274
		// this is harmless.
275
		$username = rawurldecode($username);
276
277
		if (!$username) {
278
			return false;
279
		}
280
281
		$entity = $this->entity_cache->getByUsername($username);
282
		if ($entity) {
283
			return $entity;
284
		}
285
286
		$users = $this->entities->getEntitiesFromAttributes([
287
			'types' => 'user',
288
			'attribute_name_value_pairs' => [
289
				'name' => 'username',
290
				'value' => $username,
291
			],
292
			'limit' => 1,
293
		]);
294
		return $users ? $users[0] : false;
295
	}
296
297
	/**
298
	 * Get an array of users from an email address
299
	 *
300
	 * @param string $email Email address
301
	 * @return array
302
	 */
303
	public function getByEmail($email) {
304
		if (!$email) {
305
			return [];
306
		}
307
308
		$users = $this->entities->getEntitiesFromAttributes([
309
			'types' => 'user',
310
			'attribute_name_value_pairs' => [
311
				'name' => 'email',
312
				'value' => $email,
313
			],
314
			'limit' => 1,
315
		]);
316
317
		return $users ? : [];
318
	}
319
320
	/**
321
	 * Return users (or the number of them) who have been active within a recent period.
322
	 *
323
	 * @param array $options Array of options with keys:
324
	 *
325
	 *   seconds (int)  => Length of period (default 600 = 10min)
326
	 *   limit   (int)  => Limit (default 10)
327
	 *   offset  (int)  => Offset (default 0)
328
	 *   count   (bool) => Return a count instead of users? (default false)
329
	 *
330
	 * @return \ElggUser[]|int
331
	 */
332
	public function findActive(array $options = []) {
333
	
334
		$options = array_merge([
335
			'seconds' => 600,
336
			'limit' => $this->config->default_limit,
337
		], $options);
338
339
		// cast options we're sending to hook
340
		foreach (['seconds', 'limit', 'offset'] as $key) {
341
			$options[$key] = (int) $options[$key];
342
		}
343
		$options['count'] = (bool) $options['count'];
344
345
		// allow plugins to override
346
		$params = [
347
			'seconds' => $options['seconds'],
348
			'limit' => $options['limit'],
349
			'offset' => $options['offset'],
350
			'count' => $options['count'],
351
			'options' => $options,
352
		];
353
		$data = _elgg_services()->hooks->trigger('find_active_users', 'system', $params, null);
354
		// check null because the handler could legitimately return falsey values.
355
		if ($data !== null) {
356
			return $data;
357
		}
358
359
		$dbprefix = $this->config->dbprefix;
360
		$time = $this->getCurrentTime()->getTimestamp() - $options['seconds'];
361
		return elgg_get_entities([
0 ignored issues
show
Bug Best Practice introduced by
The return type of return elgg_get_entities...'u.last_action desc')); (ElggBatch|false|integer|array) is incompatible with the return type documented by Elgg\Database\UsersTable::findActive of type ElggUser[]|integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
362
			'type' => 'user',
363
			'limit' => $options['limit'],
364
			'offset' => $options['offset'],
365
			'count' => $options['count'],
366
			'joins' => ["join {$dbprefix}users_entity u on e.guid = u.guid"],
367
			'wheres' => ["u.last_action >= {$time}"],
368
			'order_by' => "u.last_action desc",
369
		]);
370
	}
371
372
	/**
373
	 * Registers a user, returning false if the username already exists
374
	 *
375
	 * @param string $username              The username of the new user
376
	 * @param string $password              The password
377
	 * @param string $name                  The user's display name
378
	 * @param string $email                 The user's email address
379
	 * @param bool   $allow_multiple_emails Allow the same email address to be
380
	 *                                      registered multiple times?
381
	 *
382
	 * @return int|false The new user's GUID; false on failure
383
	 * @throws RegistrationException
384
	 */
385
	public function register($username, $password, $name, $email, $allow_multiple_emails = false) {
386
387
		// no need to trim password
388
		$username = trim($username);
389
		$name = trim(strip_tags($name));
390
		$email = trim($email);
391
392
		// A little sanity checking
393
		if (empty($username) || empty($password) || empty($name) || empty($email)) {
394
			return false;
395
		}
396
397
		// Make sure a user with conflicting details hasn't registered and been disabled
398
		$access_status = access_get_show_hidden_status();
399
		access_show_hidden_entities(true);
400
401
		if (!validate_email_address($email)) {
402
			throw new RegistrationException(_elgg_services()->translator->translate('registration:emailnotvalid'));
403
		}
404
405
		if (!validate_password($password)) {
406
			throw new RegistrationException(_elgg_services()->translator->translate('registration:passwordnotvalid'));
407
		}
408
409
		if (!validate_username($username)) {
410
			throw new RegistrationException(_elgg_services()->translator->translate('registration:usernamenotvalid'));
411
		}
412
413
		if ($user = get_user_by_username($username)) {
0 ignored issues
show
$user is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
414
			throw new RegistrationException(_elgg_services()->translator->translate('registration:userexists'));
415
		}
416
417
		if ((!$allow_multiple_emails) && (get_user_by_email($email))) {
418
			throw new RegistrationException(_elgg_services()->translator->translate('registration:dupeemail'));
419
		}
420
421
		access_show_hidden_entities($access_status);
422
423
		// Create user
424
		$user = new ElggUser();
425
		$user->username = $username;
426
		$user->email = $email;
427
		$user->name = $name;
428
		$user->access_id = ACCESS_PUBLIC;
429
		$user->setPassword($password);
430
		$user->owner_guid = 0; // Users aren't owned by anyone, even if they are admin created.
431
		$user->container_guid = 0; // Users aren't contained by anyone, even if they are admin created.
432
		$user->language = _elgg_services()->translator->getCurrentLanguage();
433
		if ($user->save() === false) {
434
			return false;
435
		}
436
437
		// Turn on email notifications by default
438
		$user->setNotificationSetting('email', true);
439
	
440
		return $user->getGUID();
441
	}
442
443
	/**
444
	 * Generates a unique invite code for a user
445
	 *
446
	 * @param string $username The username of the user sending the invitation
447
	 *
448
	 * @return string Invite code
449
	 * @see validateInviteCode
450
	 */
451
	public function generateInviteCode($username) {
452
		$time = $this->getCurrentTime()->getTimestamp();
453
		return "$time." . _elgg_services()->hmac->getHmac([(int) $time, $username])->getToken();
454
	}
455
456
	/**
457
	 * Validate a user's invite code
458
	 *
459
	 * @param string $username The username
460
	 * @param string $code     The invite code
461
	 *
462
	 * @return bool
463
	 * @see generateInviteCode
464
	 */
465
	public function validateInviteCode($username, $code) {
466
		// validate the format of the token created by ->generateInviteCode()
467
		if (!preg_match('~^(\d+)\.([a-zA-Z0-9\-_]+)$~', $code, $m)) {
468
			return false;
469
		}
470
		$time = $m[1];
471
		$mac = $m[2];
472
473
		return _elgg_services()->hmac->getHmac([(int) $time, $username])->matchesToken($mac);
474
	}
475
476
	/**
477
	 * Set the validation status for a user.
478
	 *
479
	 * @param int    $user_guid The user's GUID
480
	 * @param bool   $status    Validated (true) or unvalidated (false)
481
	 * @param string $method    Optional method to say how a user was validated
482
	 * @return bool
483
	 */
484
	public function setValidationStatus($user_guid, $status, $method = '') {
485
		$user = get_user($user_guid);
486
		if (!$user) {
487
			return false;
488
		}
489
490
		$result1 = create_metadata($user->guid, 'validated', (int) $status);
491
		$result2 = create_metadata($user->guid, 'validated_method', $method);
492
		if ($result1 && $result2) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result1 of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Bug Best Practice introduced by
The expression $result2 of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
493
			if ((bool) $status) {
494
				elgg_trigger_after_event('validate', 'user', $user);
495
			} else {
496
				elgg_trigger_after_event('invalidate', 'user', $user);
497
			}
498
			return true;
499
		} else {
500
			return false;
501
		}
502
	}
503
504
	/**
505
	 * Gets the validation status of a user.
506
	 *
507
	 * @param int $user_guid The user's GUID
508
	 * @return bool|null Null means status was not set for this user.
509
	 */
510
	public function getValidationStatus($user_guid) {
511
		$user = get_entity($user_guid);
512
		if (!$user || !isset($user->validated)) {
513
			return null;
514
		}
515
		return (bool) $user->validated;
516
	}
517
518
	/**
519
	 * Sets the last action time of the given user to right now.
520
	 *
521
	 * @see _elgg_session_boot The session boot calls this at the beginning of every request
522
	 *
523
	 * @param ElggUser $user User entity
524
	 * @return void
525
	 */
526
	public function setLastAction(ElggUser $user) {
527
528
		$time = $this->getCurrentTime()->getTimestamp();
529
530
		if ($user->last_action == $time) {
531
			// no change required
532
			return;
533
		}
534
535
		$query = "
536
			UPDATE {$this->table}
537
			SET
538
				prev_last_action = last_action,
539
				last_action = :last_action
540
			WHERE guid = :guid
541
		";
542
543
		$params = [
544
			':last_action' => $time,
545
			':guid' => (int) $user->guid,
546
		];
547
548
		// these writes actually work, we just type hint read-only.
549
		$user->prev_last_action = $user->last_action;
550
		$user->last_action = $time;
551
552
		execute_delayed_write_query($query, null, $params);
553
554
		$this->entity_cache->set($user);
555
556
		// If we save the user to memcache during this request, then we'll end up with the
557
		// old (incorrect) attributes cached (notice the above query is delayed). So it's
558
		// simplest to just resave the user after all plugin code runs.
559
		register_shutdown_function(function () use ($user, $time) {
560
			$this->entities->updateLastAction($user, $time); // keep entity table in sync
561
			$user->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'), $time);
562
		});
563
	}
564
565
	/**
566
	 * Sets the last logon time of the given user to right now.
567
	 *
568
	 * @param ElggUser $user User entity
569
	 * @return void
570
	 */
571
	public function setLastLogin(ElggUser $user) {
572
573
		$time = $this->getCurrentTime()->getTimestamp();
574
575
		if ($user->last_login == $time) {
576
			// no change required
577
			return;
578
		}
579
580
		$query = "
581
			UPDATE {$this->table}
582
			SET
583
				prev_last_login = last_login,
584
				last_login = :last_login
585
			WHERE guid = :guid
586
		";
587
588
		$params = [
589
			':last_login' => $time,
590
			':guid' => (int) $user->guid,
591
		];
592
593
		// these writes actually work, we just type hint read-only.
594
		$user->prev_last_login = $user->last_login;
595
		$user->last_login = $time;
596
597
		execute_delayed_write_query($query, null, $params);
598
599
		$this->entity_cache->set($user);
600
601
		// If we save the user to memcache during this request, then we'll end up with the
602
		// old (incorrect) attributes cached. Hence we want to invalidate as late as possible.
603
		// the user object gets saved
604
		register_shutdown_function(function () use ($user) {
605
			$user->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
606
		});
607
	}
608
}
609