From 3f8018060e61ebc108e3517ad8a93d86a7839551 Mon Sep 17 00:00:00 2001
From: Sascha Grossenbacher <saschagros@gmail.com>
Date: Tue, 18 Apr 2017 18:43:40 +0200
Subject: [PATCH] Split validation into multiple methods to simplify login and
 registration form customizations

---
 .../Plugin/Commerce/CheckoutPane/Login.php    | 222 ++++++++++++------
 1 file changed, 153 insertions(+), 69 deletions(-)

diff --git a/modules/checkout/src/Plugin/Commerce/CheckoutPane/Login.php b/modules/checkout/src/Plugin/Commerce/CheckoutPane/Login.php
index 90654a54ab..594239aed3 100644
--- a/modules/checkout/src/Plugin/Commerce/CheckoutPane/Login.php
+++ b/modules/checkout/src/Plugin/Commerce/CheckoutPane/Login.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Url;
 use Drupal\Core\Link;
 use Drupal\user\UserAuthInterface;
+use Drupal\user\UserInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\RequestStack;
 
@@ -195,7 +196,7 @@ public function buildPaneForm(array $pane_form, FormStateInterface $form_state,
       '#type' => 'textfield',
       '#title' => $this->t('Username'),
       '#size' => 60,
-      '#maxlength' => USERNAME_MAX_LENGTH,
+      '#maxlength' => UserInterface::USERNAME_MAX_LENGTH,
       '#attributes' => [
         'autocorrect' => 'none',
         'autocapitalize' => 'none',
@@ -259,7 +260,7 @@ public function buildPaneForm(array $pane_form, FormStateInterface $form_state,
     $pane_form['register']['name'] = [
       '#type' => 'textfield',
       '#title' => $this->t('Username'),
-      '#maxlength' => USERNAME_MAX_LENGTH,
+      '#maxlength' => UserInterface::USERNAME_MAX_LENGTH,
       '#description' => $this->t("Several special characters are allowed, including space, period (.), hyphen (-), apostrophe ('), underscore (_), and the @ sign."),
       '#required' => FALSE,
       '#attributes' => [
@@ -289,87 +290,170 @@ public function buildPaneForm(array $pane_form, FormStateInterface $form_state,
    * {@inheritdoc}
    */
   public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
-    $values = $form_state->getValue($pane_form['#parents']);
     $triggering_element = $form_state->getTriggeringElement();
+
     switch ($triggering_element['#op']) {
       case 'continue':
         return;
 
       case 'login':
-        $name_element = $pane_form['returning_customer']['name'];
-        $username = $values['returning_customer']['name'];
-        $password = trim($values['returning_customer']['password']);
-        // Generate the "reset password" url.
-        $query = !empty($username) ? ['name' => $username] : [];
-        $password_url = Url::fromRoute('user.pass', [], ['query' => $query])->toString();
-
-        if (empty($username) || empty($password)) {
-          $form_state->setError($pane_form['returning_customer'], $this->t('Unrecognized username or password. <a href=":url">Have you forgotten your password?</a>', [':url' => $password_url]));
-          return;
-        }
-        if (user_is_blocked($username)) {
-          $form_state->setError($name_element, $this->t('The username %name has not been activated or is blocked.', ['%name' => $username]));
-          return;
-        }
-        if (!$this->credentialsCheckFlood->isAllowedHost($this->clientIp)) {
-          $form_state->setErrorByName($name_element, $this->t('Too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or <a href=":url">request a new password</a>.', [':url' => Url::fromRoute('user.pass')]));
-          $this->credentialsCheckFlood->register($this->clientIp, $username);
-          return;
-        }
-        elseif (!$this->credentialsCheckFlood->isAllowedAccount($this->clientIp, $username)) {
-          $form_state->setErrorByName($name_element, $this->t('Too many failed login attempts for this account. It is temporarily blocked. Try again later or <a href=":url">request a new password</a>.', [':url' => Url::fromRoute('user.pass')]));
-          $this->credentialsCheckFlood->register($this->clientIp, $username);
-          return;
-        }
-
-        $uid = $this->userAuth->authenticate($username, $password);
-        if (!$uid) {
-          $this->credentialsCheckFlood->register($this->clientIp, $username);
-          $form_state->setError($name_element, $this->t('Unrecognized username or password. <a href=":url">Have you forgotten your password?</a>', [':url' => $password_url]));
-        }
-        $form_state->set('logged_in_uid', $uid);
+        $this->validateReturningCustomer($pane_form, $form_state, $complete_form);
         break;
 
       case 'register':
-        $email = $values['register']['mail'];
-        $username = $values['register']['name'];
-        $password = trim($values['register']['password']);
-        if (empty($email)) {
-          $form_state->setError($pane_form['register']['mail'], $this->t('Email field is required.'));
-          return;
-        }
-        if (empty($username)) {
-          $form_state->setError($pane_form['register']['name'], $this->t('Username field is required.'));
-          return;
-        }
-        if (empty($password)) {
-          $form_state->setError($pane_form['register']['password'], $this->t('Password field is required.'));
-          return;
-        }
+        $this->validateRegister($pane_form, $form_state, $complete_form);
 
-        /** @var \Drupal\user\UserInterface $account */
-        $account = $this->entityTypeManager->getStorage('user')->create([
-          'mail' => $email,
-          'name' => $username,
-          'pass' => $password,
-          'status' => TRUE,
-        ]);
-        // Validate the entity. This will ensure that the username and email
-        // are in the right format and not already taken.
-        $violations = $account->validate();
-        foreach ($violations->getByFields(['name', 'mail']) as $violation) {
-          list($field_name) = explode('.', $violation->getPropertyPath(), 2);
-          $form_state->setError($pane_form['register'][$field_name], $violation->getMessage());
-        }
-
-        if (!$form_state->hasAnyErrors()) {
-          $account->save();
-          $form_state->set('logged_in_uid', $account->id());
-        }
         break;
     }
   }
 
+  /**
+   * Validates a returning customers username and password.
+   *
+   * @param array $pane_form
+   *   The pane form, containing the following basic properties:
+   *   - #parents: Identifies the position of the pane form in the overall
+   *     parent form, and identifies the location where the field values are
+   *     placed within $form_state->getValues().
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state of the parent form.
+   * @param array $complete_form
+   *   The complete form structure.
+   */
+  protected function validateReturningCustomer(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
+    $values = $form_state->getValue($pane_form['#parents']);
+    $name_element = $pane_form['returning_customer']['name'];
+    $username = $values['returning_customer']['name'];
+    $password = trim($values['returning_customer']['password']);
+    // Generate the "reset password" url.
+    $query = !empty($username) ? ['name' => $username] : [];
+    $password_url = Url::fromRoute('user.pass', [], ['query' => $query])->toString();
+
+    if (empty($username) || empty($password)) {
+      $form_state->setError($pane_form['returning_customer'], $this->t('Unrecognized username or password. <a href=":url">Have you forgotten your password?</a>', [':url' => $password_url]));
+      return;
+    }
+    if (user_is_blocked($username)) {
+      $form_state->setError($name_element, $this->t('The username %name has not been activated or is blocked.', ['%name' => $username]));
+      return;
+    }
+    if (!$this->credentialsCheckFlood->isAllowedHost($this->clientIp)) {
+      $form_state->setErrorByName($name_element, $this->t('Too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or <a href=":url">request a new password</a>.', [':url' => Url::fromRoute('user.pass')]));
+      $this->credentialsCheckFlood->register($this->clientIp, $username);
+      return;
+    }
+    elseif (!$this->credentialsCheckFlood->isAllowedAccount($this->clientIp, $username)) {
+      $form_state->setErrorByName($name_element, $this->t('Too many failed login attempts for this account. It is temporarily blocked. Try again later or <a href=":url">request a new password</a>.', [':url' => Url::fromRoute('user.pass')]));
+      $this->credentialsCheckFlood->register($this->clientIp, $username);
+      return;
+    }
+
+    $uid = $this->userAuth->authenticate($username, $password);
+    if (!$uid) {
+      $this->credentialsCheckFlood->register($this->clientIp, $username);
+      $form_state->setError($name_element, $this->t('Unrecognized username or password. <a href=":url">Have you forgotten your password?</a>', [':url' => $password_url]));
+    }
+    $form_state->set('logged_in_uid', $uid);
+  }
+
+  /**
+   * Validates the registration form and creates a new user.
+   *
+   * @param array $pane_form
+   *   The pane form, containing the following basic properties:
+   *   - #parents: Identifies the position of the pane form in the overall
+   *     parent form, and identifies the location where the field values are
+   *     placed within $form_state->getValues().
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state of the parent form.
+   * @param array $complete_form
+   *   The complete form structure.
+   */
+  protected function validateRegister(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
+    if ($user_values = $this->getAndValidateRegisterUserValues($pane_form, $form_state)) {
+
+      /** @var \Drupal\user\UserInterface $account */
+      $account = $this->entityTypeManager->getStorage('user')->create($user_values);
+
+      // Validate the entity. This will ensure that the username and email
+      // are in the right format and not already taken.
+      $violations = $account->validate();
+      foreach ($violations->getByFields(['name', 'mail']) as $violation) {
+        list($field_name) = explode('.', $violation->getPropertyPath(), 2);
+        $form_state->setError($pane_form['register'][$field_name], $violation->getMessage());
+      }
+
+      if (!$form_state->hasAnyErrors()) {
+        $account->save();
+        $form_state->set('logged_in_uid', $account->id());
+      }
+    }
+  }
+
+  /**
+   * Validates form values for the register form.
+   *
+   * @param array $pane_form
+   *   The pane form, containing the following basic properties:
+   *   - #parents: Identifies the position of the pane form in the overall
+   *     parent form, and identifies the location where the field values are
+   *     placed within $form_state->getValues().
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state of the parent form.
+   *
+   * @return array|null
+   *   Either an array of values that can be used to create a new user or NULL
+   *   if there are validation errors.
+   */
+  protected function getAndValidateRegisterUserValues(array &$pane_form, FormStateInterface $form_state) {
+    $values = $form_state->getValue($pane_form['#parents']);
+
+    $user_values = [
+      'status' => TRUE
+    ];
+
+    // If the e-mail, username and password fields are visible, ensure they
+    // have a value and add them to the user values. This allows subclasses and
+    // form alters to hide certain form elements and then provide alternative
+    // values, for example to set the username based on the e-mail.
+    $email_element = $pane_form['register']['mail'];
+    if (!isset($email_element['#access']) || $email_element['#access']) {
+      $email = $values['register']['mail'];
+      if (empty($email)) {
+        $form_state->setError($email_element, $this->t('Email field is required.'));
+        return NULL;
+      }
+      else {
+        $user_values['mail'] = $email;
+      }
+    }
+
+    $name_element = $pane_form['register']['name'];
+    if (!isset($name_element['#access']) || $name_element['#access']) {
+      $username = $values['register']['name'];
+      if (empty($username)) {
+        $form_state->setError($name_element, $this->t('Username field is required.'));
+        return NULL;
+      }
+      else {
+        $user_values['name'] = $username;
+      }
+    }
+
+    $password_element = $pane_form['register']['password'];
+    if (!isset($password_element['#access']) || $password_element['#access']) {
+      $password = trim($values['register']['password']);
+      if (empty($password)) {
+        $form_state->setError($pane_form['register']['password'], $this->t('Password field is required.'));
+        return NULL;
+      }
+      else {
+        $user_values['pass'] = $password;
+      }
+    }
+    return $user_values;
+  }
+
   /**
    * {@inheritdoc}
    */