From 0f4e5a80033c3066506ee5ab8e17d2ca4531b902 Mon Sep 17 00:00:00 2001
From: skaught <skaught@gmail.com>
Date: Tue, 5 Apr 2016 16:26:08 -0400
Subject: [PATCH] issue 2514730 -- dependent field error messages

---
 core/modules/user/src/AccountForm.php | 103 +++++++++++++++++++++++++---------
 1 file changed, 76 insertions(+), 27 deletions(-)

diff --git a/core/modules/user/src/AccountForm.php b/core/modules/user/src/AccountForm.php
index 9199c73..facd8d6 100644
--- a/core/modules/user/src/AccountForm.php
+++ b/core/modules/user/src/AccountForm.php
@@ -15,6 +15,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Url;
 use Drupal\language\ConfigurableLanguageManagerInterface;
 use Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUser;
 use Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUserAdmin;
@@ -86,19 +87,7 @@ public function form(array $form, FormStateInterface $form_state) {
       '#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 email address to be edited and deleted.
-    // Also see \Drupal\user\Plugin\Validation\Constraint\UserMailRequired.
-    $form['account']['mail'] = array(
-      '#type' => 'email',
-      '#title' => $this->t('Email address'),
-      '#description' => $this->t('A valid email address. All emails from the system will be sent to this address. The email 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 email.'),
-      '#required' => !(!$account->getEmail() && $user->hasPermission('administer users')),
-      '#default_value' => (!$register ? $account->getEmail() : ''),
-    );
-
-    // Only show name field on registration form or user can change own username.
+     // Only show name field on registration form or user can change own username.
     $form['account']['name'] = array(
       '#type' => 'textfield',
       '#title' => $this->t('Username'),
@@ -111,25 +100,46 @@ public function form(array $form, FormStateInterface $form_state) {
         'autocapitalize' => 'off',
         'spellcheck' => 'false',
       ),
+      '#weight' => -10,
       '#default_value' => (!$register ? $account->getUsername() : ''),
       '#access' => ($register || ($user->id() == $account->id() && $user->hasPermission('change own username')) || $admin),
     );
 
+    // 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 email address to be edited and deleted.
+    // Also see \Drupal\user\Plugin\Validation\Constraint\UserMailRequired.
+    $form['account']['mail'] = array(
+      '#type' => 'email',
+      '#weight' => -9,
+      '#title' => $this->t('Email address'),
+      '#description' => $this->t('A valid email address. All emails from the system will be sent to this address. The email 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 email.'),
+      '#required' => !(!$account->getEmail() && $user->hasPermission('administer users')),
+      '#default_value' => (!$register ? $account->getEmail() : ''),
+    );
+
+
+
     // Display password field only for existing users or when user is allowed to
     // assign a password during registration.
     if (!$register) {
-      $form['account']['pass'] = array(
+      $form['account']['change-password'] = array(
+        '#type' => 'fieldset',
+        '#title' => $this->t('Change Password'),
+      );
+      $form['account']['change-password']['pass-note'] = array(
+        '#markup' => '<p>' . $this->t('To change the current user password, enter the new password in both fields.') . '</p>',
+      );
+      $form['account']['change-password']['pass'] = array(
         '#type' => 'password_confirm',
         '#size' => 25,
-        '#description' => $this->t('To change the current user password, enter the new password in both fields.'),
       );
 
       // To skip the current password field, the user must have logged in via a
       // one-time link and have the token in the URL. Store this in $form_state
       // so it persists even on subsequent Ajax requests.
-      if (!$form_state->get('user_pass_reset') && ($token = $this->getRequest()->get('pass-reset-token'))) {
-        $session_key = 'pass_reset_' . $account->id();
-        $user_pass_reset = isset($_SESSION[$session_key]) && Crypt::hashEquals($_SESSION[$session_key], $token);
+      if (!$form_state->get('user_pass_reset')) {
+        $user_pass_reset = isset($_SESSION['pass_reset_' . $account->id()]) && Crypt::hashEquals($_SESSION['pass_reset_' . $account->id()], \Drupal::request()->query->get('pass-reset-token'));
         $form_state->set('user_pass_reset', $user_pass_reset);
       }
 
@@ -151,7 +161,7 @@ public function form(array $form, FormStateInterface $form_state) {
         // The user may only change their own password without their current
         // password if they logged in via a one-time login link.
         if (!$form_state->get('user_pass_reset')) {
-          $form['account']['current_pass']['#description'] = $this->t('Required if you want to change the %mail or %pass below. <a href=":request_new_url" title="Send password reset instructions via email.">Reset your password</a>.', array(
+          $form['account']['current_pass']['#description'] = $this->t('Required if you want to change the %mail or %pass fields.  <br>If you have lost your current password you can <a href=":request_new_url" title="Send password reset instructions via email.">reset your password</a> by have this site emailing your current known email address.', array(
             '%mail' => $form['account']['mail']['#title'],
             '%pass' => $this->t('Password'),
             ':request_new_url' => $this->url('user.pass'),
@@ -185,22 +195,27 @@ public function form(array $form, FormStateInterface $form_state) {
       $status = $config->get('register') == USER_REGISTER_VISITORS ? 1 : 0;
     }
 
-    $form['account']['status'] = array(
+    $roles = array_map(array('\Drupal\Component\Utility\Html', 'escape'), user_role_names(TRUE));
+    $role_access = ($roles && $user->hasPermission('administer permissions') ? TRUE : FALSE);
+    $form['account']['account-states'] = array(
+      '#type' => 'details',
+      '#title' => 'Account Access Settings',
+      '#open' => TRUE,
+      '#access' => ($admin || $role_access),
+    );
+    $form['account']['account-states']['status'] = array(
       '#type' => 'radios',
       '#title' => $this->t('Status'),
       '#default_value' => $status,
       '#options' => array($this->t('Blocked'), $this->t('Active')),
       '#access' => $admin,
     );
-
-    $roles = array_map(array('\Drupal\Component\Utility\Html', 'escape'), user_role_names(TRUE));
-
-    $form['account']['roles'] = array(
+    $form['account']['account-states']['roles'] = array(
       '#type' => 'checkboxes',
       '#title' => $this->t('Roles'),
       '#default_value' => (!$register ? $account->getRoles() : array()),
       '#options' => $roles,
-      '#access' => $roles && $user->hasPermission('administer permissions'),
+      '#access' => $role_access,
     );
 
     // Special handling for the inevitable "Authenticated user" role.
@@ -271,7 +286,6 @@ public function form(array $form, FormStateInterface $form_state) {
     // use-cases where this synchronization is not desired, a module can alter
     // or remove this item.
     $form['#entity_builders']['sync_user_langcode'] = [$this, 'syncUserLangcode'];
-
     return parent::form($form, $form_state, $account);
   }
 
@@ -376,9 +390,44 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
       'preferred_langcode',
       'preferred_admin_langcode'
     );
+    $parent_links = FALSE;
     foreach ($violations->getByFields($field_names) as $violation) {
       list($field_name) = explode('.', $violation->getPropertyPath(), 2);
-      $form_state->setErrorByName($field_name, $violation->getMessage());
+      $errors_on_parent = FALSE;
+      if ($field_name == 'pass') {
+        $errors_on_parent['pass']['name'] = $this->t('Change Password');
+        $errors_on_parent['pass']['id'] = 'edit-pass-pass1';
+      }
+      if ($field_name == 'mail') {
+        $errors_on_parent['mail']['name'] = $this->t('Email address');
+        $errors_on_parent['mail']['id'] = 'edit-mail';
+      }
+      if ($errors_on_parent) {
+        foreach ($errors_on_parent as $field_name => $item) {
+          // Build link with fragment.
+          $url = Url::fromRoute('<current>', [], ['fragment' => $item['id']]);
+          $parent_links[] = \Drupal::l($item['name'], $url);
+        }
+        $password_url = Url::fromRoute('<current>', [], ['fragment' => 'edit-current-pass']);
+        $password_link = \Drupal::l($this->t('Current password'), $password_url);
+        $form_state->setErrorByName($field_name, $this->t("Your @current_password is missing or incorrect; it's required to change the @name.", array('@current_password' => $password_link, '@name' => $item['name'])));
+      }
+      else {
+        $form_state->setErrorByName($field_name, $violation->getMessage());
+      }
+    }
+    if ($parent_links) {
+      // It is expected that no more than 2 errors will be joined this way.
+      $message = $this->formatPlural(
+        count($parent_links),
+        '@first_error needs your current password in order to submit.',
+        '@first_error and @second_error need your current password in order to submit.',
+        array(
+          '@first_error' => $parent_links['0'],
+          '@second_error' => (isset($parent_links['1']) ? $parent_links['1'] : FALSE)
+        )
+      );
+      $form_state->setErrorByName('current_pass', $message);
     }
     parent::flagViolations($violations, $form, $form_state);
   }
-- 
2.6.4

