Elgg  Version 3.0
Accounts.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Users;
4 
5 use Elgg\Config;
11 use ElggUser;
13 
17 class Accounts {
18 
22  protected $config;
23 
27  protected $translator;
28 
32  protected $passwords;
33 
37  protected $users;
38 
42  protected $hooks;
43 
53  public function __construct(
59  ) {
60  $this->config = $config;
61  $this->translator = $translator;
62  $this->passwords = $passwords;
63  $this->users = $users;
64  $this->hooks = $hooks;
65  }
66 
80  public function validateAccountData($username, $password, $name, $email, $allow_multiple_emails = false) {
81 
82  return elgg_call(ELGG_SHOW_DISABLED_ENTITIES, function () use ($username, $email, $password, $name, $allow_multiple_emails) {
83  $results = new ValidationResults();
84 
85  if (empty($name)) {
86  $error = $this->translator->translate('registration:noname');
87  $results->fail('name', $name, $error);
88  } else {
89  $results->pass('name', $name);
90  }
91 
92  try {
93  $this->assertValidEmail($email, !$allow_multiple_emails);
94 
95  $results->pass('email', $email);
96  } catch (RegistrationException $ex) {
97  $results->fail('email', $email, $ex->getMessage());
98  }
99 
100  try {
102 
103  $results->pass('password', $password);
104  } catch (RegistrationException $ex) {
105  $results->fail('password', $password, $ex->getMessage());
106  }
107 
108  try {
109  $this->assertValidUsername($username, true);
110 
111  $results->pass('username', $username);
112  } catch (RegistrationException $ex) {
113  $results->fail('username', $username, $ex->getMessage());
114  }
115 
116  return $results;
117  });
118  }
119 
133  public function assertValidAccountData($username, $password, $name, $email, $allow_multiple_emails = false) {
134 
135  $results = $this->validateAccountData($username, $password, $name, $email, $allow_multiple_emails);
136 
137  foreach ($results->all() as $result) {
138  if (!$result->isValid()) {
139  throw new RegistrationException($result->getError());
140  }
141  }
142 
143  }
144 
159  public function register($username, $password, $name, $email, $allow_multiple_emails = false, $subtype = null) {
160 
161  $this->assertValidAccountData($username, $password, $name, $email, $allow_multiple_emails);
162 
163  // Create user
164  $constructor = ElggUser::class;
165  if (isset($subtype)) {
167  if ($class && class_exists($class) && is_subclass_of($class, ElggUser::class)) {
168  $constructor = $class;
169  }
170  }
171 
172  $user = new $constructor();
173  /* @var $user ElggUser */
174 
175  if (isset($subtype)) {
176  $user->subtype = $subtype;
177  }
178 
179  $user->username = $username;
180  $user->email = $email;
181  $user->name = $name;
182  $user->access_id = ACCESS_PUBLIC;
183  $user->owner_guid = 0; // Users aren't owned by anyone, even if they are admin created.
184  $user->container_guid = 0; // Users aren't contained by anyone, even if they are admin created.
185  $user->language = $this->translator->getCurrentLanguage();
186 
187  if ($user->save() === false) {
188  return false;
189  }
190 
191  // doing this after save to prevent metadata save notices on unwritable metadata password_hash
192  $user->setPassword($password);
193 
194  // Turn on email notifications by default
195  $user->setNotificationSetting('email', true);
196 
197  return $user->getGUID();
198  }
199 
211  public function assertValidUsername($username, $assert_unregistered = false) {
212 
213  if (strlen($username) < $this->config->minusername) {
214  $msg = $this->translator->translate('registration:usernametooshort', [$this->config->minusername]);
215  throw new RegistrationException($msg);
216  }
217 
218  // username in the database has a limit of 128 characters
219  if (strlen($username) > 128) {
220  $msg = $this->translator->translate('registration:usernametoolong', [128]);
221  throw new RegistrationException($msg);
222  }
223 
224  // Blacklist for bad characters (partially nicked from mediawiki)
225  $blacklist = '/[' .
226  '\x{0080}-\x{009f}' . // iso-8859-1 control chars
227  '\x{00a0}' . // non-breaking space
228  '\x{2000}-\x{200f}' . // various whitespace
229  '\x{2028}-\x{202f}' . // breaks and control chars
230  '\x{3000}' . // ideographic space
231  '\x{e000}-\x{f8ff}' . // private use
232  ']/u';
233 
234  if (preg_match($blacklist, $username)) {
235  // @todo error message needs work
236  throw new RegistrationException($this->translator->translate('registration:invalidchars'));
237  }
238 
239  // Belts and braces
240  // @todo Tidy into main unicode
241  $blacklist2 = '\'/\\"*& ?#%^(){}[]~?<>;|¬`@+=,:';
242 
243  $blacklist2 = $this->hooks->trigger(
244  'username:character_blacklist',
245  'user',
246  ['blacklist' => $blacklist2],
247  $blacklist2
248  );
249 
250  for ($n = 0; $n < strlen($blacklist2); $n++) {
251  if (strpos($username, $blacklist2[$n]) !== false) {
252  $msg = $this->translator->translate('registration:invalidchars', [$blacklist2[$n], $blacklist2]);
253  $msg = htmlspecialchars($msg, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
254  throw new RegistrationException($msg);
255  }
256  }
257 
258  $result = $this->hooks->trigger(
259  'registeruser:validate:username',
260  'all',
261  ['username' => $username],
262  true
263  );
264 
265  if (!$result) {
266  throw new RegistrationException($this->translator->translate('registration:usernamenotvalid'));
267  }
268 
269  if ($assert_unregistered) {
270  $exists = elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function () use ($username) {
271  return $this->users->getByUsername($username);
272  });
273 
274  if ($exists) {
275  throw new RegistrationException($this->translator->translate('registration:userexists'));
276  }
277  }
278  }
279 
289  public function assertValidPassword($password) {
290 
291  if (is_array($password)) {
292  list($password, $password2) = $password;
293 
294  if (empty($password) || empty($password2)) {
295  throw new RegistrationException(elgg_echo('RegistrationException:EmptyPassword'));
296  }
297 
298  if (strcmp($password, $password2) != 0) {
299  throw new RegistrationException(elgg_echo('RegistrationException:PasswordMismatch'));
300  }
301  }
302 
303  if (strlen($password) < $this->config->min_password_length) {
304  $msg = $this->translator->translate('registration:passwordtooshort', [$this->config->min_password_length]);
305  throw new RegistrationException($msg);
306  }
307 
308  $result = $this->hooks->trigger(
309  'registeruser:validate:password',
310  'all',
311  ['password' => $password],
312  true
313  );
314 
315  if (!$result) {
316  throw new RegistrationException($this->translator->translate('registration:passwordnotvalid'));
317  }
318  }
319 
329  public function assertCurrentPassword(ElggUser $user, $password) {
330  if (!$this->passwords->verify($password, $user->password_hash)) {
331  throw new RegistrationException($this->translator->translate('LoginException:PasswordFailure'));
332  }
333  }
334 
344  public function assertValidEmail($address, $assert_unregistered = false) {
345  if (!$this->isValidEmail($address)) {
346  throw new RegistrationException($this->translator->translate('registration:notemail'));
347  }
348 
349  $result = $this->hooks->trigger(
350  'registeruser:validate:email',
351  'all',
352  ['email' => $address],
353  true
354  );
355 
356  if (!$result) {
357  throw new RegistrationException($this->translator->translate('registration:emailnotvalid'));
358  }
359 
360  if ($assert_unregistered) {
361  $exists = elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function () use ($address) {
362  return $this->users->getByEmail($address);
363  });
364 
365  if ($exists) {
366  throw new RegistrationException($this->translator->translate('registration:dupeemail'));
367  }
368  }
369  }
370 
378  public function isValidEmail($address) {
379  return filter_var($address, FILTER_VALIDATE_EMAIL) === $address;
380  }
381 }
elgg_call(int $flags, Closure $closure)
Calls a callable autowiring the arguments using public DI services and applying logic based on flags...
Definition: elgglib.php:1176
__construct(Config $config, Translator $translator, PasswordService $passwords, UsersTable $users, PluginHooksService $hooks)
Constructor.
Definition: Accounts.php:53
if(!$user||!$user->canDelete()) $name
Definition: delete.php:22
$username
Definition: delete.php:23
Represents a set of validated parameters.
$subtype
Definition: delete.php:22
$email
Definition: register.php:18
assertValidPassword($password)
Simple validation of a password.
Definition: Accounts.php:289
assertValidAccountData($username, $password, $name, $email, $allow_multiple_emails=false)
Assert that given registration details are valid and can be used to register the user.
Definition: Accounts.php:133
$error
Bad request error.
Definition: 400.php:6
if(!$user||!$user->canEdit()) $password
$class
Definition: field.php:29
const ELGG_SHOW_DISABLED_ENTITIES
Definition: constants.php:158
elgg_get_entity_class($type, $subtype)
Procedural code for creating, loading, and modifying objects.
Definition: entities.php:16
$user
Definition: ban.php:7
elgg ElggUser
Definition: ElggUser.js:12
assertValidUsername($username, $assert_unregistered=false)
Simple function which ensures that a username contains only valid characters.
Definition: Accounts.php:211
assertValidEmail($address, $assert_unregistered=false)
Simple validation of a email.
Definition: Accounts.php:344
class
Definition: placeholder.php:21
WARNING: API IN FLUX.
Definition: UsersTable.php:22
const ACCESS_PUBLIC
Definition: constants.php:14
User accounts service.
Definition: Accounts.php:17
validateAccountData($username, $password, $name, $email, $allow_multiple_emails=false)
Validate registration details to ensure they can be used to register a new user account.
Definition: Accounts.php:80