AccountFormController.php 14.7 KB
Newer Older
1
2
3
4
<?php

/**
 * @file
5
 * Contains \Drupal\user\AccountFormController.
6
7
8
9
 */

namespace Drupal\user;

10
use Drupal\Core\Entity\ContentEntityFormController;
11
use Drupal\Core\Entity\EntityManagerInterface;
12
use Drupal\Core\Language\Language;
13
14
use Drupal\Core\Language\LanguageManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
15
16
17
18

/**
 * Form controller for the user account forms.
 */
19
abstract class AccountFormController extends ContentEntityFormController {
20

21
22
23
24
25
26
27
28
29
30
  /**
   * The language manager.
   *
   * @var \Drupal\Core\Language\LanguageManager
   */
  protected $languageManager;

  /**
   * Constructs a new EntityFormController object.
   *
31
32
   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
   *   The entity manager.
33
34
35
   * @param \Drupal\Core\Language\LanguageManager $language_manager
   *   The language manager.
   */
36
37
  public function __construct(EntityManagerInterface $entity_manager, LanguageManager $language_manager) {
    parent::__construct($entity_manager);
38
39
40
41
42
43
44
45
    $this->languageManager = $language_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
46
      $container->get('entity.manager'),
47
48
49
50
      $container->get('language_manager')
    );
  }

51
  /**
52
   * {@inheritdoc}
53
   */
54
55
  public function form(array $form, array &$form_state) {
    $account = $this->entity;
56
    $user = $this->currentUser();
57
    $config = \Drupal::config('user.settings');
58

59
    $language_interface = language(Language::TYPE_INTERFACE);
60
    $register = $account->isAnonymous();
61
62
63
64
65
66
67
68
69
70
71
    $admin = user_access('administer users');

    // Account information.
    $form['account'] = array(
      '#type'   => 'container',
      '#weight' => -10,
    );

    // Only show name field on registration form or user can change own username.
    $form['account']['name'] = array(
      '#type' => 'textfield',
72
      '#title' => $this->t('Username'),
73
      '#maxlength' => USERNAME_MAX_LENGTH,
74
      '#description' => $this->t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.'),
75
      '#required' => TRUE,
76
77
      '#attributes' => array('class' => array('username'), 'autocorrect' => 'off', 'autocomplete' => 'off', 'autocapitalize' => 'off',
      'spellcheck' => 'false'),
78
      '#default_value' => (!$register ? $account->getUsername() : ''),
79
      '#access' => ($register || ($user->id() == $account->id() && user_access('change own username')) || $admin),
80
81
82
83
84
85
86
87
      '#weight' => -10,
    );

    // The mail field is NOT required if account originally had no mail set
    // and the user performing the edit has 'administer users' permission.
    // This allows users without e-mail address to be edited and deleted.
    $form['account']['mail'] = array(
      '#type' => 'email',
88
89
      '#title' => $this->t('E-mail address'),
      '#description' => $this->t('A valid e-mail address. All e-mails from the system will be sent to this address. The e-mail address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by e-mail.'),
90
91
      '#required' => !(!$account->getEmail() && user_access('administer users')),
      '#default_value' => (!$register ? $account->getEmail() : ''),
92
93
94
95
96
97
98
99
100
      '#attributes' => array('autocomplete' => 'off'),
    );

    // Display password field only for existing users or when user is allowed to
    // assign a password during registration.
    if (!$register) {
      $form['account']['pass'] = array(
        '#type' => 'password_confirm',
        '#size' => 25,
101
        '#description' => $this->t('To change the current user password, enter the new password in both fields.'),
102
        '#pre_render' => array('user_form_pre_render_password_confirm'),
103
104
105
106
      );

      // To skip the current password field, the user must have logged in via a
      // one-time link and have the token in the URL.
107
108
      $pass_reset = isset($_SESSION['pass_reset_' . $account->id()]) && (\Drupal::request()->query->get('pass-reset-token') == $_SESSION['pass_reset_' . $account->id()]);

109
110
111
112
113
114
115
      $protected_values = array();
      $current_pass_description = '';

      // The user may only change their own password without their current
      // password if they logged in via a one-time login link.
      if (!$pass_reset) {
        $protected_values['mail'] = $form['account']['mail']['#title'];
116
117
118
        $protected_values['pass'] = $this->t('Password');
        $request_new = l($this->t('Request new password'), 'user/password', array('attributes' => array('title' => $this->t('Request new password via e-mail.'))));
        $current_pass_description = $this->t('Required if you want to change the %mail or %pass below. !request_new.', array('%mail' => $protected_values['mail'], '%pass' => $protected_values['pass'], '!request_new' => $request_new));
119
120
121
      }

      // The user must enter their current password to change to a new one.
122
      if ($user->id() == $account->id()) {
123
124
125
126
127
128
129
        $form['account']['current_pass_required_values'] = array(
          '#type' => 'value',
          '#value' => $protected_values,
        );

        $form['account']['current_pass'] = array(
          '#type' => 'password',
130
          '#title' => $this->t('Current password'),
131
132
133
134
          '#size' => 25,
          '#access' => !empty($protected_values),
          '#description' => $current_pass_description,
          '#weight' => -5,
135
136
137
          // Do not let web browsers remember this password, since we are
          // trying to confirm that the person submitting the form actually
          // knows the current one.
138
139
140
141
142
143
144
          '#attributes' => array('autocomplete' => 'off'),
        );

        $form_state['user'] = $account;
        $form['#validate'][] = 'user_validate_current_pass';
      }
    }
145
    elseif (!$config->get('verify_mail') || $admin) {
146
147
148
      $form['account']['pass'] = array(
        '#type' => 'password_confirm',
        '#size' => 25,
149
        '#description' => $this->t('Provide a password for the new account in both fields.'),
150
        '#pre_render' => array('user_form_pre_render_password_confirm'),
151
152
153
154
155
        '#required' => TRUE,
      );
    }

    if ($admin) {
156
      $status = $account->isActive();
157
158
    }
    else {
159
      $status = $register ? $config->get('register') == USER_REGISTER_VISITORS : $account->isActive();
160
161
162
163
    }

    $form['account']['status'] = array(
      '#type' => 'radios',
164
      '#title' => $this->t('Status'),
165
      '#default_value' => $status,
166
      '#options' => array($this->t('Blocked'), $this->t('Active')),
167
168
169
      '#access' => $admin,
    );

170
    $roles = array_map('check_plain', user_role_names(TRUE));
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
    // The disabled checkbox subelement for the 'authenticated user' role
    // must be generated separately and added to the checkboxes element,
    // because of a limitation in Form API not supporting a single disabled
    // checkbox within a set of checkboxes.
    // @todo This should be solved more elegantly. See issue #119038.
    $checkbox_authenticated = array(
      '#type' => 'checkbox',
      '#title' => $roles[DRUPAL_AUTHENTICATED_RID],
      '#default_value' => TRUE,
      '#disabled' => TRUE,
    );
    unset($roles[DRUPAL_AUTHENTICATED_RID]);

    $form['account']['roles'] = array(
      '#type' => 'checkboxes',
186
      '#title' => $this->t('Roles'),
187
      '#default_value' => (!$register ? $account->getRoles() : array()),
188
189
190
191
192
193
194
      '#options' => $roles,
      '#access' => $roles && user_access('administer permissions'),
      DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated,
    );

    $form['account']['notify'] = array(
      '#type' => 'checkbox',
195
      '#title' => $this->t('Notify user of new account'),
196
197
198
199
200
      '#access' => $register && $admin,
    );

    // Signature.
    $form['signature_settings'] = array(
201
      '#type' => 'details',
202
      '#title' => $this->t('Signature settings'),
203
      '#weight' => 1,
204
      '#access' => (!$register && $config->get('signatures')),
205
206
207
208
    );

    $form['signature_settings']['signature'] = array(
      '#type' => 'text_format',
209
      '#title' => $this->t('Signature'),
210
      '#default_value' => $account->getSignature(),
211
      '#description' => $this->t('Your signature will be publicly displayed at the end of your comments.'),
212
      '#format' => $account->getSignatureFormat(),
213
214
    );

215
    $user_preferred_langcode = $register ? $language_interface->id : $account->getPreferredLangcode();
216

217
    $user_preferred_admin_langcode = $register ? $language_interface->id : $account->getPreferredAdminLangcode();
218

219
220
    // Is default the interface language?
    include_once DRUPAL_ROOT . '/core/includes/language.inc';
221
    $interface_language_is_default = language_negotiation_method_get_first(Language::TYPE_INTERFACE) != LANGUAGE_NEGOTIATION_SELECTED;
222
    $form['language'] = array(
223
      '#type' => $this->languageManager->isMultilingual() ? 'details' : 'container',
224
      '#title' => $this->t('Language settings'),
225
226
227
228
      // Display language selector when either creating a user on the admin
      // interface or editing a user account.
      '#access' => !$register || user_access('administer users'),
    );
229

230
231
    $form['language']['preferred_langcode'] = array(
      '#type' => 'language_select',
232
      '#title' => $this->t('Site language'),
233
      '#languages' => Language::STATE_CONFIGURABLE,
234
      '#default_value' => $user_preferred_langcode,
235
      '#description' => $interface_language_is_default ? $this->t("This account's preferred language for e-mails and site presentation.") : $this->t("This account's preferred language for e-mails."),
236
    );
237

238
239
240
241
242
243
244
245
246
247
248
    // Only show the account setting for Administration pages language to users
    // if one of the detection and selection methods uses it.
    $show_admin_language = FALSE;
    if ($this->moduleHandler->moduleExists('language') && $this->languageManager->isMultilingual()) {
      foreach (language_types_info() as $type_key => $language_type) {
        $negotiation_settings = variable_get("language_negotiation_{$type_key}", array());
        if ($show_admin_language = isset($negotiation_settings[LANGUAGE_NEGOTIATION_USER_ADMIN])) {
          break;
        }
      }
    }
249
250
    $form['language']['preferred_admin_langcode'] = array(
      '#type' => 'language_select',
251
      '#title' => $this->t('Administration pages language'),
252
      '#languages' => Language::STATE_CONFIGURABLE,
253
      '#default_value' => $user_preferred_admin_langcode,
254
      '#access' => $show_admin_language && user_access('access administration pages', $account),
255
    );
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
    // User entities contain both a langcode property (for identifying the
    // language of the entity data) and a preferred_langcode property (see
    // above). Rather than provide a UI forcing the user to choose both
    // separately, assume that the user profile data is in the user's preferred
    // language. This element provides that synchronization. For use-cases where
    // this synchronization is not desired, a module can alter or remove this
    // element.
    $form['language']['langcode'] = array(
      '#type' => 'value',
      '#value_callback' => '_user_language_selector_langcode_value',
      // For the synchronization to work, this element must have a larger weight
      // than the preferred_langcode element. Set a large weight here in case
      // a module alters the weight of the other element.
      '#weight' => 100,
    );

    return parent::form($form, $form_state, $account);
  }

275
276
277
278
279
280
281
282
283
  /**
   * {@inheritdoc}
   */
  public function buildEntity(array $form, array &$form_state) {
    // Change the roles array to a list of enabled roles.
    // @todo: Alter the form state as the form values are directly extracted and
    //   set on the field, which throws an exception as the list requires
    //   numeric keys. Allow to override this per field. As this function is
    //   called twice, we have to prevent it from getting the array keys twice.
284
285

    if (is_string(key($form_state['values']['roles']))) {
286
287
288
289
290
      $form_state['values']['roles'] = array_keys(array_filter($form_state['values']['roles']));
    }
    return parent::buildEntity($form, $form_state);
  }

291
  /**
292
   * Overrides Drupal\Core\Entity\EntityFormController::submit().
293
294
295
296
   */
  public function validate(array $form, array &$form_state) {
    parent::validate($form, $form_state);

297
    $account = $this->entity;
298
299
300
    // Validate new or changing username.
    if (isset($form_state['values']['name'])) {
      if ($error = user_validate_name($form_state['values']['name'])) {
301
        $this->setFormError('name', $form_state, $error);
302
303
304
305
306
307
      }
      // Cast the user ID as an integer. It might have been set to NULL, which
      // could lead to unexpected results.
      else {
        $name_taken = (bool) db_select('users')
        ->fields('users', array('uid'))
308
        ->condition('uid', (int) $account->id(), '<>')
309
310
311
312
313
314
        ->condition('name', db_like($form_state['values']['name']), 'LIKE')
        ->range(0, 1)
        ->execute()
        ->fetchField();

        if ($name_taken) {
315
          $this->setFormError('name', $form_state, $this->t('The name %name is already taken.', array('%name' => $form_state['values']['name'])));
316
317
318
319
320
321
322
323
324
        }
      }
    }

    $mail = $form_state['values']['mail'];

    if (!empty($mail)) {
      $mail_taken = (bool) db_select('users')
      ->fields('users', array('uid'))
325
      ->condition('uid', (int) $account->id(), '<>')
326
327
328
329
330
331
332
      ->condition('mail', db_like($mail), 'LIKE')
      ->range(0, 1)
      ->execute()
      ->fetchField();

      if ($mail_taken) {
        // Format error message dependent on whether the user is logged in or not.
333
        if ($GLOBALS['user']->isAuthenticated()) {
334
          $this->setFormError('mail', $form_state, $this->t('The e-mail address %email is already taken.', array('%email' => $mail)));
335
336
        }
        else {
337
          $this->setFormError('mail', $form_state, $this->t('The e-mail address %email is already registered. <a href="@password">Have you forgotten your password?</a>', array('%email' => $mail, '@password' => url('user/password'))));
338
339
340
341
342
343
344
345
346
347
348
349
350
351
        }
      }
    }

    // Make sure the signature isn't longer than the size of the database field.
    // Signatures are disabled by default, so make sure it exists first.
    if (isset($form_state['values']['signature'])) {
      // Move text format for user signature into 'signature_format'.
      $form_state['values']['signature_format'] = $form_state['values']['signature']['format'];
      // Move text value for user signature into 'signature'.
      $form_state['values']['signature'] = $form_state['values']['signature']['value'];

      $user_schema = drupal_get_schema('users');
      if (drupal_strlen($form_state['values']['signature']) > $user_schema['fields']['signature']['length']) {
352
        $this->setFormError('signature', $form_state, $this->t('The signature is too long: it must be %max characters or less.', array('%max' => $user_schema['fields']['signature']['length'])));
353
354
355
      }
    }
  }
356

357
}