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