1<?php
2
3namespace Drupal\Core\Render\Element;
4
5use Drupal\Core\Form\FormStateInterface;
6
7/**
8 * Provides a form element for double-input of passwords.
9 *
10 * Formats as a pair of password fields, which do not validate unless the two
11 * entered passwords match.
12 *
13 * Properties:
14 * - #size: The size of the input element in characters.
15 *
16 * Usage example:
17 * @code
18 * $form['pass'] = array(
19 *   '#type' => 'password_confirm',
20 *   '#title' => $this->t('Password'),
21 *   '#size' => 25,
22 * );
23 * @endcode
24 *
25 * @see \Drupal\Core\Render\Element\Password
26 *
27 * @FormElement("password_confirm")
28 */
29class PasswordConfirm extends FormElement {
30
31  /**
32   * {@inheritdoc}
33   */
34  public function getInfo() {
35    $class = get_class($this);
36    return [
37      '#input' => TRUE,
38      '#markup' => '',
39      '#process' => [
40        [$class, 'processPasswordConfirm'],
41      ],
42      '#theme_wrappers' => ['form_element'],
43    ];
44  }
45
46  /**
47   * {@inheritdoc}
48   */
49  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
50    if ($input === FALSE) {
51      $element += ['#default_value' => []];
52      return $element['#default_value'] + ['pass1' => '', 'pass2' => ''];
53    }
54    $value = ['pass1' => '', 'pass2' => ''];
55    // Throw out all invalid array keys; we only allow pass1 and pass2.
56    foreach ($value as $allowed_key => $default) {
57      // These should be strings, but allow other scalars since they might be
58      // valid input in programmatic form submissions. Any nested array values
59      // are ignored.
60      if (isset($input[$allowed_key]) && is_scalar($input[$allowed_key])) {
61        $value[$allowed_key] = (string) $input[$allowed_key];
62      }
63    }
64    return $value;
65  }
66
67  /**
68   * Expand a password_confirm field into two text boxes.
69   */
70  public static function processPasswordConfirm(&$element, FormStateInterface $form_state, &$complete_form) {
71    $element['pass1'] = [
72      '#type' => 'password',
73      '#title' => t('Password'),
74      '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'],
75      '#required' => $element['#required'],
76      '#attributes' => [
77        'class' => ['password-field', 'js-password-field'],
78        'autocomplete' => ['new-password'],
79      ],
80      '#error_no_message' => TRUE,
81    ];
82    $element['pass2'] = [
83      '#type' => 'password',
84      '#title' => t('Confirm password'),
85      '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'],
86      '#required' => $element['#required'],
87      '#attributes' => [
88        'class' => ['password-confirm', 'js-password-confirm'],
89        'autocomplete' => ['new-password'],
90      ],
91      '#error_no_message' => TRUE,
92    ];
93    $element['#element_validate'] = [[get_called_class(), 'validatePasswordConfirm']];
94    $element['#tree'] = TRUE;
95
96    if (isset($element['#size'])) {
97      $element['pass1']['#size'] = $element['pass2']['#size'] = $element['#size'];
98    }
99
100    return $element;
101  }
102
103  /**
104   * Validates a password_confirm element.
105   */
106  public static function validatePasswordConfirm(&$element, FormStateInterface $form_state, &$complete_form) {
107    $pass1 = trim($element['pass1']['#value']);
108    $pass2 = trim($element['pass2']['#value']);
109    if (strlen($pass1) > 0 || strlen($pass2) > 0) {
110      if (strcmp($pass1, $pass2)) {
111        $form_state->setError($element, t('The specified passwords do not match.'));
112      }
113    }
114    elseif ($element['#required'] && $form_state->getUserInput()) {
115      $form_state->setError($element, t('Password field is required.'));
116    }
117
118    // Password field must be converted from a two-element array into a single
119    // string regardless of validation results.
120    $form_state->setValueForElement($element['pass1'], NULL);
121    $form_state->setValueForElement($element['pass2'], NULL);
122    $form_state->setValueForElement($element, $pass1);
123
124    return $element;
125  }
126
127}
128