User.php 16.8 KB
Newer Older
1
2
<?php

3
namespace Drupal\user\Entity;
4

5
use Drupal\Core\Entity\ContentEntityBase;
6
use Drupal\Core\Entity\EntityChangedTrait;
7
use Drupal\Core\Entity\EntityStorageInterface;
8
use Drupal\Core\Entity\EntityTypeInterface;
9
use Drupal\Core\Field\BaseFieldDefinition;
10
use Drupal\Core\Language\LanguageInterface;
11
use Drupal\user\RoleInterface;
12
use Drupal\user\StatusItem;
13
use Drupal\user\TimeZoneItem;
14
use Drupal\user\UserInterface;
15
16
17

/**
 * Defines the user entity class.
18
 *
19
20
21
 * The base table name here is plural, despite Drupal table naming standards,
 * because "user" is a reserved word in many databases.
 *
22
 * @ContentEntityType(
23
24
 *   id = "user",
 *   label = @Translation("User"),
25
26
27
28
29
30
31
 *   label_collection = @Translation("Users"),
 *   label_singular = @Translation("user"),
 *   label_plural = @Translation("users"),
 *   label_count = @PluralTranslation(
 *     singular = "@count user",
 *     plural = "@count users",
 *   ),
32
 *   handlers = {
33
 *     "storage" = "Drupal\user\UserStorage",
34
 *     "storage_schema" = "Drupal\user\UserStorageSchema",
35
 *     "access" = "Drupal\user\UserAccessControlHandler",
36
 *     "list_builder" = "Drupal\user\UserListBuilder",
37
 *     "views_data" = "Drupal\user\UserViewsData",
38
39
40
 *     "route_provider" = {
 *       "html" = "Drupal\user\Entity\UserRouteProvider",
 *     },
41
 *     "form" = {
42
 *       "default" = "Drupal\user\ProfileForm",
43
 *       "cancel" = "Drupal\user\Form\UserCancelForm",
44
 *       "register" = "Drupal\user\RegisterForm"
45
 *     },
46
 *     "translation" = "Drupal\user\ProfileTranslationHandler"
47
 *   },
48
 *   admin_permission = "administer users",
49
 *   base_table = "users",
50
 *   data_table = "users_field_data",
51
 *   translatable = TRUE,
52
53
 *   entity_keys = {
 *     "id" = "uid",
54
 *     "langcode" = "langcode",
55
 *     "uuid" = "uuid"
56
57
 *   },
 *   links = {
58
59
60
 *     "canonical" = "/user/{user}",
 *     "edit-form" = "/user/{user}/edit",
 *     "cancel-form" = "/user/{user}/cancel",
61
 *     "collection" = "/admin/people",
62
 *   },
63
64
 *   field_ui_base_route = "entity.user.admin_form",
 *   common_reference_target = TRUE
65
 * )
66
 */
67
class User extends ContentEntityBase implements UserInterface {
68

69
70
  use EntityChangedTrait;

71
72
73
74
75
76
77
  /**
   * Stores a reference for a reusable anonymous user entity.
   *
   * @var \Drupal\user\UserInterface
   */
  protected static $anonymousUser;

78
79
80
81
82
83
84
  /**
   * {@inheritdoc}
   */
  public function isNew() {
    return !empty($this->enforceIsNew) || $this->id() === NULL;
  }

85
86
87
88
89
90
91
  /**
   * {@inheritdoc}
   */
  public function label() {
    return $this->getDisplayName();
  }

92
93
94
  /**
   * {@inheritdoc}
   */
95
96
  public function preSave(EntityStorageInterface $storage) {
    parent::preSave($storage);
97

98
99
    // Make sure that the authenticated/anonymous roles are not persisted.
    foreach ($this->get('roles') as $index => $item) {
100
      if (in_array($item->target_id, [RoleInterface::ANONYMOUS_ID, RoleInterface::AUTHENTICATED_ID])) {
101
102
103
104
        $this->get('roles')->offsetUnset($index);
      }
    }

105
    // Store account cancellation information.
106
    foreach (['user_cancel_method', 'user_cancel_notify'] as $key) {
107
108
109
110
111
112
113
114
115
      if (isset($this->{$key})) {
        \Drupal::service('user.data')->set('user', $this->id(), substr($key, 5), $this->{$key});
      }
    }
  }

  /**
   * {@inheritdoc}
   */
116
117
  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
    parent::postSave($storage, $update);
118

119
    if ($update) {
120
      $session_manager = \Drupal::service('session_manager');
121
122
123
      // If the password has been changed, delete all open sessions for the
      // user and recreate the current one.
      if ($this->pass->value != $this->original->pass->value) {
124
        $session_manager->delete($this->id());
125
        if ($this->id() == \Drupal::currentUser()->id()) {
126
          \Drupal::service('session')->migrate();
127
128
129
130
131
        }
      }

      // If the user was blocked, delete the user's sessions to force a logout.
      if ($this->original->status->value != $this->status->value && $this->status->value == 0) {
132
        $session_manager->delete($this->id());
133
134
135
136
137
138
      }

      // Send emails after we have the new user object.
      if ($this->status->value != $this->original->status->value) {
        // The user's status is changing; conditionally send notification email.
        $op = $this->status->value == 1 ? 'status_activated' : 'status_blocked';
139
        _user_mail_notify($op, $this);
140
141
142
143
144
145
146
      }
    }
  }

  /**
   * {@inheritdoc}
   */
147
148
  public static function postDelete(EntityStorageInterface $storage, array $entities) {
    parent::postDelete($storage, $entities);
149

150
151
152
153
    $uids = array_keys($entities);
    \Drupal::service('user.data')->delete(NULL, $uids);
  }

154
155
156
  /**
   * {@inheritdoc}
   */
157
  public function getRoles($exclude_locked_roles = FALSE) {
158
    $roles = [];
159

160
161
162
    // Users with an ID always have the authenticated user role.
    if (!$exclude_locked_roles) {
      if ($this->isAuthenticated()) {
163
        $roles[] = RoleInterface::AUTHENTICATED_ID;
164
165
      }
      else {
166
        $roles[] = RoleInterface::ANONYMOUS_ID;
167
168
169
      }
    }

170
    foreach ($this->get('roles') as $role) {
171
      if ($role->target_id) {
172
        $roles[] = $role->target_id;
173
      }
174
    }
175

176
177
178
    return $roles;
  }

179
180
181
182
183
184
185
186
187
188
189
  /**
   * {@inheritdoc}
   */
  public function hasRole($rid) {
    return in_array($rid, $this->getRoles());
  }

  /**
   * {@inheritdoc}
   */
  public function addRole($rid) {
190

191
    if (in_array($rid, [RoleInterface::AUTHENTICATED_ID, RoleInterface::ANONYMOUS_ID])) {
192
193
194
195
      throw new \InvalidArgumentException('Anonymous or authenticated role ID must not be assigned manually.');
    }

    $roles = $this->getRoles(TRUE);
196
197
198
199
200
201
202
203
    $roles[] = $rid;
    $this->set('roles', array_unique($roles));
  }

  /**
   * {@inheritdoc}
   */
  public function removeRole($rid) {
204
    $this->set('roles', array_diff($this->getRoles(TRUE), [$rid]));
205
206
  }

207
208
209
210
211
212
213
214
215
  /**
   * {@inheritdoc}
   */
  public function hasPermission($permission) {
    // User #1 has all privileges.
    if ((int) $this->id() === 1) {
      return TRUE;
    }

216
    return $this->getRoleStorage()->isPermissionInRoles($permission, $this->getRoles());
217
218
  }

219
220
221
222
223
224
225
226
227
228
229
230
  /**
   * {@inheritdoc}
   */
  public function getPassword() {
    return $this->get('pass')->value;
  }

  /**
   * {@inheritdoc}
   */
  public function setPassword($password) {
    $this->get('pass')->value = $password;
231
    return $this;
232
233
234
235
236
237
238
239
240
241
242
243
244
245
  }

  /**
   * {@inheritdoc}
   */
  public function getEmail() {
    return $this->get('mail')->value;
  }

  /**
   * {@inheritdoc}
   */
  public function setEmail($mail) {
    $this->get('mail')->value = $mail;
246
    return $this;
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
  }

  /**
   * {@inheritdoc}
   */
  public function getCreatedTime() {
    return $this->get('created')->value;
  }

  /**
   * {@inheritdoc}
   */
  public function getLastAccessedTime() {
    return $this->get('access')->value;
  }

  /**
   * {@inheritdoc}
   */
  public function setLastAccessTime($timestamp) {
    $this->get('access')->value = $timestamp;
268
    return $this;
269
270
271
272
273
274
275
276
277
278
279
280
281
282
  }

  /**
   * {@inheritdoc}
   */
  public function getLastLoginTime() {
    return $this->get('login')->value;
  }

  /**
   * {@inheritdoc}
   */
  public function setLastLoginTime($timestamp) {
    $this->get('login')->value = $timestamp;
283
    return $this;
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
  }

  /**
   * {@inheritdoc}
   */
  public function isActive() {
    return $this->get('status')->value == 1;
  }

  /**
   * {@inheritdoc}
   */
  public function isBlocked() {
    return $this->get('status')->value == 0;
  }

  /**
   * {@inheritdoc}
   */
  public function activate() {
    $this->get('status')->value = 1;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function block() {
    $this->get('status')->value = 0;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getTimeZone() {
    return $this->get('timezone')->value;
  }

  /**
   * {@inheritdoc}
   */
326
  public function getPreferredLangcode($fallback_to_default = TRUE) {
327
    $language_list = $this->languageManager()->getLanguages();
328
329
    $preferred_langcode = $this->get('preferred_langcode')->value;
    if (!empty($preferred_langcode) && isset($language_list[$preferred_langcode])) {
330
      return $language_list[$preferred_langcode]->getId();
331
332
    }
    else {
333
      return $fallback_to_default ? $this->languageManager()->getDefaultLanguage()->getId() : '';
334
335
336
337
338
339
    }
  }

  /**
   * {@inheritdoc}
   */
340
  public function getPreferredAdminLangcode($fallback_to_default = TRUE) {
341
    $language_list = $this->languageManager()->getLanguages();
342
343
    $preferred_langcode = $this->get('preferred_admin_langcode')->value;
    if (!empty($preferred_langcode) && isset($language_list[$preferred_langcode])) {
344
      return $language_list[$preferred_langcode]->getId();
345
346
    }
    else {
347
      return $fallback_to_default ? $this->languageManager()->getDefaultLanguage()->getId() : '';
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getInitialEmail() {
    return $this->get('init')->value;
  }

  /**
   * {@inheritdoc}
   */
  public function isAuthenticated() {
    return $this->id() > 0;
  }
364

365
366
367
368
369
370
371
372
373
374
375
  /**
   * {@inheritdoc}
   */
  public function isAnonymous() {
    return $this->id() == 0;
  }

  /**
   * {@inheritdoc}
   */
  public function getUsername() {
376
    @trigger_error('\Drupal\Core\Session\AccountInterface::getUsername() is deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Core\Session\AccountInterface::getAccountName() or \Drupal\user\UserInterface::getDisplayName() instead. See https://www.drupal.org/node/2572493', E_USER_DEPRECATED);
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
    return $this->getAccountName();
  }

  /**
   * {@inheritdoc}
   */
  public function getAccountName() {
    return $this->get('name')->value ?: '';
  }

  /**
   * {@inheritdoc}
   */
  public function getDisplayName() {
    $name = $this->getAccountName() ?: \Drupal::config('user.settings')->get('anonymous');
392
393
394
395
    \Drupal::moduleHandler()->alter('user_format_name', $name, $this);
    return $name;
  }

396
397
398
399
400
401
402
403
  /**
   * {@inheritdoc}
   */
  public function setUsername($username) {
    $this->set('name', $username);
    return $this;
  }

404
405
406
407
408
409
410
411
412
413
414
  /**
   * {@inheritdoc}
   */
  public function setExistingPassword($password) {
    $this->get('pass')->existing = $password;
  }

  /**
   * {@inheritdoc}
   */
  public function checkExistingPassword(UserInterface $account_unchanged) {
415
    return strlen($this->get('pass')->existing) > 0 && \Drupal::service('password')->check(trim($this->get('pass')->existing), $account_unchanged->getPassword());
416
417
  }

418
419
420
421
422
423
424
425
426
427
428
  /**
   * Returns an anonymous user entity.
   *
   * @return \Drupal\user\UserInterface
   *   An anonymous user entity.
   */
  public static function getAnonymousUser() {
    if (!isset(static::$anonymousUser)) {

      // @todo Use the entity factory once available, see
      //   https://www.drupal.org/node/1867228.
429
430
      $entity_type_manager = \Drupal::entityTypeManager();
      $entity_type = $entity_type_manager->getDefinition('user');
431
432
      $class = $entity_type->getClass();

433
434
435
      static::$anonymousUser = new $class([
        'uid' => [LanguageInterface::LANGCODE_DEFAULT => 0],
        'name' => [LanguageInterface::LANGCODE_DEFAULT => ''],
436
437
        // Explicitly set the langcode to ensure that field definitions do not
        // need to be fetched to figure out a default.
438
        'langcode' => [LanguageInterface::LANGCODE_DEFAULT => LanguageInterface::LANGCODE_NOT_SPECIFIED],
439
      ], $entity_type->id());
440
441
442
443
    }
    return clone static::$anonymousUser;
  }

444
445
446
  /**
   * {@inheritdoc}
   */
447
  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
448
449
450
451
452
453
454
455
456
    /** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
    $fields = parent::baseFieldDefinitions($entity_type);

    $fields['uid']->setLabel(t('User ID'))
      ->setDescription(t('The user ID.'));

    $fields['uuid']->setDescription(t('The user UUID.'));

    $fields['langcode']->setLabel(t('Language code'))
457
      ->setDescription(t('The user language code.'))
458
      ->setDisplayOptions('form', ['region' => 'hidden']);
459

460
    $fields['preferred_langcode'] = BaseFieldDefinition::create('language')
461
      ->setLabel(t('Preferred language code'))
462
463
464
      ->setDescription(t("The user's preferred language code for receiving emails and viewing the site."))
      // @todo: Define this via an options provider once
      // https://www.drupal.org/node/2329937 is completed.
465
466
467
      ->addPropertyConstraints('value', [
        'AllowedValues' => ['callback' => __CLASS__ . '::getAllowedConfigurableLanguageCodes'],
      ]);
468

469
    $fields['preferred_admin_langcode'] = BaseFieldDefinition::create('language')
470
      ->setLabel(t('Preferred admin language code'))
471
      ->setDescription(t("The user's preferred language code for viewing administration pages."))
472
473
474
      // @todo: A default value of NULL is ignored, so we have to specify
      // an empty field item structure instead. Fix this in
      // https://www.drupal.org/node/2318605.
475
      ->setDefaultValue([0 => ['value' => NULL]])
476
477
      // @todo: Define this via an options provider once
      // https://www.drupal.org/node/2329937 is completed.
478
479
480
      ->addPropertyConstraints('value', [
        'AllowedValues' => ['callback' => __CLASS__ . '::getAllowedConfigurableLanguageCodes'],
      ]);
481

482
483
    // The name should not vary per language. The username is the visual
    // identifier for a user and needs to be consistent in all languages.
484
    $fields['name'] = BaseFieldDefinition::create('string')
485
486
      ->setLabel(t('Name'))
      ->setDescription(t('The name of this user.'))
487
      ->setRequired(TRUE)
488
      ->setConstraints([
489
        // No Length constraint here because the UserName constraint also covers
490
        // that.
491
492
493
        'UserName' => [],
        'UserNameUnique' => [],
      ]);
494
    $fields['name']->getItemDefinition()->setClass('\Drupal\user\UserNameItem');
495

496
    $fields['pass'] = BaseFieldDefinition::create('password')
497
      ->setLabel(t('Password'))
498
499
      ->setDescription(t('The password of this user (hashed).'))
      ->addConstraint('ProtectedUserField');
500

501
    $fields['mail'] = BaseFieldDefinition::create('email')
502
503
      ->setLabel(t('Email'))
      ->setDescription(t('The email of this user.'))
504
      ->setDefaultValue('')
505
      ->addConstraint('UserMailUnique')
506
507
      ->addConstraint('UserMailRequired')
      ->addConstraint('ProtectedUserField');
508

509
    $fields['timezone'] = BaseFieldDefinition::create('string')
510
511
      ->setLabel(t('Timezone'))
      ->setDescription(t('The timezone of this user.'))
512
513
514
      ->setSetting('max_length', 32)
      // @todo: Define this via an options provider once
      // https://www.drupal.org/node/2329937 is completed.
515
516
517
      ->addPropertyConstraints('value', [
        'AllowedValues' => ['callback' => __CLASS__ . '::getAllowedTimezones'],
      ]);
518
    $fields['timezone']->getItemDefinition()->setClass(TimeZoneItem::class);
519

520
    $fields['status'] = BaseFieldDefinition::create('boolean')
521
      ->setLabel(t('User status'))
522
      ->setDescription(t('Whether the user is active or blocked.'))
523
      ->setDefaultValue(FALSE);
524
    $fields['status']->getItemDefinition()->setClass(StatusItem::class);
525

526
    $fields['created'] = BaseFieldDefinition::create('created')
527
528
529
      ->setLabel(t('Created'))
      ->setDescription(t('The time that the user was created.'));

530
531
    $fields['changed'] = BaseFieldDefinition::create('changed')
      ->setLabel(t('Changed'))
532
533
      ->setDescription(t('The time that the user was last edited.'))
      ->setTranslatable(TRUE);
534

535
    $fields['access'] = BaseFieldDefinition::create('timestamp')
536
537
      ->setLabel(t('Last access'))
      ->setDescription(t('The time that the user last accessed the site.'))
538
      ->setDefaultValue(0);
539

540
    $fields['login'] = BaseFieldDefinition::create('timestamp')
541
542
      ->setLabel(t('Last login'))
      ->setDescription(t('The time that the user last logged in.'))
543
      ->setDefaultValue(0);
544

545
    $fields['init'] = BaseFieldDefinition::create('email')
546
547
      ->setLabel(t('Initial email'))
      ->setDescription(t('The email address used for initial account creation.'))
548
      ->setDefaultValue('');
549

550
    $fields['roles'] = BaseFieldDefinition::create('entity_reference')
551
      ->setLabel(t('Roles'))
552
      ->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED)
553
554
      ->setDescription(t('The roles the user has.'))
      ->setSetting('target_type', 'user_role');
555
556

    return $fields;
557
558
  }

559
560
561
562
563
564
565
  /**
   * Returns the role storage object.
   *
   * @return \Drupal\user\RoleStorageInterface
   *   The role storage object.
   */
  protected function getRoleStorage() {
566
    return \Drupal::entityTypeManager()->getStorage('user_role');
567
568
  }

569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
  /**
   * Defines allowed timezones for the field's AllowedValues constraint.
   *
   * @return string[]
   *   The allowed values.
   */
  public static function getAllowedTimezones() {
    return array_keys(system_time_zones());
  }

  /**
   * Defines allowed configurable language codes for AllowedValues constraints.
   *
   * @return string[]
   *   The allowed values.
   */
  public static function getAllowedConfigurableLanguageCodes() {
    return array_keys(\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_CONFIGURABLE));
  }

589
}