Skip to content
Snippets Groups Projects
Commit eb5b42a5 authored by Youri van Koppen's avatar Youri van Koppen Committed by Greg Knaddison
Browse files

Issue #3016156 by MegaChriz, jidrone, jonathanshaw, Martijn de Wit, lkacenja:...

Issue #3016156 by MegaChriz, jidrone, jonathanshaw, Martijn de Wit, lkacenja: Multiple issues with Commerce 2 login checkout pane
parent d5e3db6b
Branches
Tags
No related merge requests found
......@@ -38,6 +38,9 @@ $settings['locale_custom_strings_en'][''] = [
'Unrecognized username or password. <a href=":password">Forgot your password?</a>' => 'Unrecognized e-mail address or password. <a href=":password">Forgot your password?</a>',
];
* If you use Drupal Commerce, adjust your checkout flow to use the alternative
login pane provided by this module.
BUGS, FEATURES, QUESTIONS
-------------------------
Post any bugs, features or questions to the issue queue:
......
......@@ -30,5 +30,8 @@
"support": {
"issues": "http://drupal.org/project/issues/email_registration",
"source": "http://cgit.drupalcode.org/email_registration"
},
"require-dev": {
"drupal/commerce": "^2.0"
}
}
......@@ -5,3 +5,14 @@ email_registration.settings:
login_with_username:
type: boolean
label: 'Allow users to log in with email address or username.'
# Commerce.
commerce_checkout.commerce_checkout_pane.email_registration_login:
type: commerce_checkout_pane_configuration
mapping:
allow_guest_checkout:
type: boolean
label: 'Allow guest checkout'
allow_registration:
type: boolean
label: 'Allow registration'
......@@ -2,10 +2,18 @@
namespace Drupal\email_registration\Plugin\Commerce\CheckoutPane;
use Drupal\commerce\CredentialsCheckFloodInterface;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\Login;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\Email;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\user\UserAuthInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\Core\Config\ImmutableConfig;
/**
* Provides the email registration login pane.
......@@ -18,14 +26,62 @@ use Drupal\Core\Url;
*/
class EmailRegistrationLogin extends Login {
/**
* Constructs a new EmailRegistrationLogin object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface $checkout_flow
* The parent checkout flow.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\commerce\CredentialsCheckFloodInterface $credentials_check_flood
* The credentials check flood controller.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\user\UserAuthInterface $user_auth
* The user authentication object.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Config\ImmutableConfig $config
* The email registration settings.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, CheckoutFlowInterface $checkout_flow, EntityTypeManagerInterface $entity_type_manager, CredentialsCheckFloodInterface $credentials_check_flood, AccountInterface $current_user, UserAuthInterface $user_auth, RequestStack $request_stack, ImmutableConfig $config) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $checkout_flow, $entity_type_manager, $credentials_check_flood, $current_user, $user_auth, $request_stack);
$this->config = $config;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, CheckoutFlowInterface $checkout_flow = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$checkout_flow,
$container->get('entity_type.manager'),
$container->get('commerce.credentials_check_flood'),
$container->get('current_user'),
$container->get('user.auth'),
$container->get('request_stack'),
$container->get('config.factory')->get('email_registration.settings')
);
}
/**
* {@inheritdoc}
*/
public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
$pane_form = parent::buildPaneForm($pane_form, $form_state, $complete_form);
$config = \Drupal::config('email_registration.settings');
$login_with_username = $config->get('login_with_username');
$login_with_username = $this->config->get('login_with_username');
$pane_form['returning_customer']['name']['#title'] = $login_with_username ? t('Email address or username') : t('Email address');
$pane_form['returning_customer']['name']['#description'] = $login_with_username ? t('Enter your email address or username.') : t('Enter your email address.');
$pane_form['returning_customer']['name']['#element_validate'][] = 'email_registration_user_login_validate';
......@@ -45,63 +101,71 @@ class EmailRegistrationLogin extends Login {
* {@inheritdoc}
*/
public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
$subformId = $pane_form['#parents'][0];
$values = $form_state->getValue($pane_form['#parents']);
$triggering_element = $form_state->getTriggeringElement();
if ($triggering_element['#op'] === 'login') {
$mail = $form_state->getValue([
'email_registration_login',
'returning_customer',
'name',
]);
$mail = $values['returning_customer']['name'];
if (!empty($mail)) {
$config = \Drupal::config('email_registration.settings');
if ($user = user_load_by_mail($mail)) {
$username = $user->getAccountName();
$form_state->setValue([
'email_registration_login',
'returning_customer',
'name',
], $username);
}
elseif (!$config->get('login_with_username')) {
// Try to load the user by mail.
$user = user_load_by_mail($mail);
}
if (empty($user)) {
// Check if users are allowed to login with username as well.
if (!$this->config->get('login_with_username')) {
// Users are not allowed to login with username. Since no user was
// found with the specified mail address, fail with an error and
// bail out.
$user_input = $form_state->getUserInput();
$query = isset($user_input['email_registration_login']['returning_customer']['name']) ? ['name' => $user_input['email_registration_login']['returning_customer']['name']] : [];
$form_state->setError($pane_form['returning_customer'], t('Unrecognized email address or password. <a href=":password">Forgot your password?</a>', [
$query = isset($user_input[$subformId]['returning_customer']['name']) ? ['name' => $user_input[$subformId]['returning_customer']['name']] : [];
$form_state->setError($pane_form['returning_customer'], $this->t('Unrecognized email address or password. <a href=":password">Forgot your password?</a>', [
':password' => Url::fromRoute('user.pass', [], ['query' => $query])
->toString(),
]));
return;
}
}
$name_element = $pane_form['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 (user_is_blocked($username)) {
$form_state->setError($name_element, $this->t('The account with email address %mail has not been activated or is blocked.', ['%mail' => $mail]));
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;
}
else {
// We have found an user! Save username on the form state, as that is
// what the parent class expects in their submit handler.
$username = $user->getAccountName();
$form_state->setValue([
$subformId,
'returning_customer',
'name',
], $username);
// Perform several checks for this user account
// Copied from parent to override error messages.
$name_element = $pane_form['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();
$uid = $this->userAuth->authenticate($username, $password);
if (!$uid) {
$this->credentialsCheckFlood->register($this->clientIp, $username);
$form_state->setError($name_element, $this->t('Unrecognized email address or password. <a href=":password">Forgot your password?</a>', [':url' => $password_url]));
if (user_is_blocked($username)) {
$form_state->setError($name_element, $this->t('The account with email address %mail has not been activated or is blocked.', ['%mail' => $mail]));
return;
}
$uid = $this->userAuth->authenticate($username, $password);
if (!$uid) {
$this->credentialsCheckFlood->register($this->clientIp, $username);
// Changing the wrong credentials error message.
if (!$this->config->get('login_with_username')) {
$form_state->setError($name_element, $this->t('Unrecognized email address or password. <a href=":password">Forgot your password?</a>', [':url' => $password_url]));
// Adding return to avoid the parent error when password is empty.
return;
}
else {
$form_state->setError($name_element, $this->t('Unrecognized username, email, or password. <a href=":url">Have you forgotten your password?</a>', [':url' => $password_url]));
}
}
}
$form_state->set('logged_in_uid', $uid);
}
parent::validatePaneForm($pane_form, $form_state, $complete_form);
}
......
<?php
namespace Drupal\Tests\email_registration\Functional\Plugin\Commerce\CheckoutPane;
use Drupal\commerce_product\Entity\ProductInterface;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Session\AccountInterface;
use Drupal\Tests\commerce\Functional\CommerceBrowserTestBase;
/**
* Tests the login checkout pane.
*
* @group email_registration
*/
class EmailRegistrationLoginTest extends CommerceBrowserTestBase {
/**
* The product.
*
* @var \Drupal\commerce_product\Entity\ProductInterface
*/
protected $product;
/**
* {@inheritdoc}
*/
public static $modules = [
'commerce_product',
'commerce_order',
'commerce_cart',
'commerce_checkout',
'email_registration',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Place commerce blocks.
$this->placeBlock('commerce_cart');
$this->placeBlock('commerce_checkout_progress');
// Create a product with variation.
$variation = $this->createEntity('commerce_product_variation', [
'type' => 'default',
'sku' => strtolower($this->randomMachineName()),
'price' => [
'number' => 9.99,
'currency_code' => 'USD',
],
]);
/** @var \Drupal\commerce_product\Entity\ProductInterface $product */
$this->product = $this->createEntity('commerce_product', [
'type' => 'default',
'title' => 'My product',
'variations' => [$variation],
'stores' => [$this->store],
]);
// Enable the email_registration_login pane and disable the default login
// pane.
/** @var \Drupal\commerce_checkout\Entity\CheckoutFlowInterface $checkout_flow */
$checkout_flow = $this->container
->get('entity_type.manager')
->getStorage('commerce_checkout_flow')
->load('default');
/** @var \Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface $checkout_flow_plugin */
$checkout_flow_plugin = $checkout_flow->getPlugin();
/** @var \Drupal\email_registration\Plugin\Commerce\CheckoutPane\EmailRegistrationLogin $pane */
$er_login_pane = $checkout_flow_plugin->getPane('email_registration_login');
$er_login_pane->setConfiguration([]);
$er_login_pane->setStepId('login');
/** @var \Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\Login $pane */
$login_pane = $checkout_flow_plugin->getPane('login');
$login_pane->setStepId('_disabled');
// Save pane settings.
$checkout_flow_plugin_configuration = $checkout_flow_plugin->getConfiguration();
$checkout_flow_plugin_configuration['panes']['email_registration_login'] = $er_login_pane->getConfiguration();
$checkout_flow_plugin_configuration['panes']['login'] = $login_pane->getConfiguration();
$checkout_flow_plugin->setConfiguration($checkout_flow_plugin_configuration);
$checkout_flow->save();
$this->drupalLogout();
$this->addProductToCart($this->product);
}
/**
* Tests if an user can login with their email address.
*/
public function testLoginWithMailAddress() {
// Create an user to login with.
$account = $this->drupalCreateUser();
$this->goToCheckout();
$this->assertCheckoutProgressStep('Login');
$edit = [
'email_registration_login[returning_customer][name]' => $account->getEmail(),
'email_registration_login[returning_customer][password]' => $account->passRaw,
];
$this->submitForm($edit, 'Log in');
$this->assertCheckoutProgressStep('Order information');
$this->assertLoggedIn($account);
}
/**
* Tests if an user can login with their username.
*/
public function testLoginWithUsername() {
// Allow users to login with their username.
$this->config('email_registration.settings')
->set('login_with_username', TRUE)
->save();
// Create an user to login with.
$account = $this->drupalCreateUser();
$this->goToCheckout();
$this->assertCheckoutProgressStep('Login');
$this->assertSession()->pageTextContains('Email address or username');
$edit = [
'email_registration_login[returning_customer][name]' => $account->getUsername(),
'email_registration_login[returning_customer][password]' => $account->passRaw,
];
$this->submitForm($edit, 'Log in');
$this->assertCheckoutProgressStep('Order information');
$this->assertLoggedIn($account);
}
/**
* Tests trying to login without entering name or mail address.
*/
public function testLoginWithoutValues() {
$this->goToCheckout();
$this->assertCheckoutProgressStep('Login');
$this->submitForm([], 'Log in');
$this->assertSession()->pageTextContains('Unrecognized email address or password. Forgot your password?');
}
/**
* Tests trying to login with wrong password.
*/
public function testFailedLogin() {
// Create an user to login with.
$account = $this->drupalCreateUser();
$this->goToCheckout();
$this->assertCheckoutProgressStep('Login');
$edit = [
'email_registration_login[returning_customer][name]' => $account->getEmail(),
'email_registration_login[returning_customer][password]' => $account->passRaw . 'foo',
];
$this->submitForm($edit, 'Log in');
$this->assertSession()->pageTextContains('Unrecognized email address or password. Forgot your password?');
}
/**
* Tests if an user cannot login with their username when that is forbidden.
*
* Thus when the option 'login_with_username' is disabled.
*/
public function testFailedLoginWithUsername() {
// Create an user to login with.
$account = $this->drupalCreateUser();
// Don't allow users to login with their username.
$this->config('email_registration.settings')
->set('login_with_username', FALSE)
->save();
$this->goToCheckout();
$this->assertCheckoutProgressStep('Login');
$this->assertSession()->pageTextContains('Enter your email address.');
$edit = [
'email_registration_login[returning_customer][name]' => $account->getUsername(),
'email_registration_login[returning_customer][password]' => $account->passRaw,
];
$this->submitForm($edit, 'Log in');
$this->assertSession()->pageTextContains('Unrecognized email address or password. Forgot your password?');
}
/**
* Tests failed login using mail when logging in with username is allowed.
*
* When the option 'login_with_username' is enabled and trying to login with
* an existing mail address, the error message should say "Unrecognized
* username or password."
*/
public function testFailedLoginWithMailAddressWhenUsernameIsAllowed() {
// Create an user to login with.
$account = $this->drupalCreateUser();
// Don't allow users to login with their username.
$this->config('email_registration.settings')
->set('login_with_username', TRUE)
->save();
$this->goToCheckout();
$this->assertCheckoutProgressStep('Login');
$edit = [
'email_registration_login[returning_customer][name]' => $account->getEmail(),
'email_registration_login[returning_customer][password]' => $account->passRaw . 'foo',
];
$this->submitForm($edit, 'Log in');
$this->assertSession()->pageTextContains('Unrecognized username, email, or password. Have you forgotten your password?');
}
/**
* Tests trying to login in with an mail address belonging to a blocked user.
*/
public function testFailedLoginWithMailAddressWithBlockedUser() {
// Create an user to login with.
$account = $this->drupalCreateUser();
$account->status = FALSE;
$account->save();
$this->goToCheckout();
$this->assertCheckoutProgressStep('Login');
// Try logging in with wrong pass first.
$edit = [
'email_registration_login[returning_customer][name]' => $account->getEmail(),
'email_registration_login[returning_customer][password]' => $account->passRaw . 'foo',
];
$this->submitForm($edit, 'Log in');
$this->assertSession()->pageTextContains('The account with email address ' . $account->getEmail() . ' has not been activated or is blocked.');
// Now try to login with right password. Same error message should come up.
$edit = [
'email_registration_login[returning_customer][name]' => $account->getEmail(),
'email_registration_login[returning_customer][password]' => $account->passRaw,
];
$this->submitForm($edit, 'Log in');
$this->assertSession()->pageTextContains('The account with email address ' . $account->getEmail() . ' has not been activated or is blocked.');
}
/**
* {@inheritdoc}
*/
protected function drupalLogin(AccountInterface $account) {
if ($this->loggedInUser) {
$this->drupalLogout();
}
$this->drupalGet('user/login');
$this->submitForm([
'mail' => $account->getEmail(),
'pass' => $account->passRaw,
], t('Log in'));
$this->assertLoggedIn($account);
}
/**
* Adds the given product to the cart.
*
* @param \Drupal\commerce_product\Entity\ProductInterface $product
* The product to add to the cart.
*/
protected function addProductToCart(ProductInterface $product) {
$this->drupalGet($product->toUrl()->toString());
$this->submitForm([], 'Add to cart');
}
/**
* Proceeds to checkout.
*/
protected function goToCheckout() {
$cart_link = $this->getSession()->getPage()->findLink('your cart');
$cart_link->click();
$this->submitForm([], 'Checkout');
}
/**
* Asserts the current step in the checkout progress block.
*
* @param string $expected
* The expected value.
*/
protected function assertCheckoutProgressStep($expected) {
$current_step = $this->getSession()->getPage()->find('css', '.checkout-progress--step__current')->getText();
$this->assertEquals($expected, $current_step);
}
/**
* Asserts that a particular user is logged in.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account to check for being logged in.
*/
protected function assertLoggedIn(AccountInterface $account) {
$account->sessionId = $this->getSession()->getCookie(\Drupal::service('session_configuration')->getOptions(\Drupal::request())['name']);
$this->assertTrue($this->drupalUserIsLoggedIn($account), new FormattableMarkup('User %name successfully logged in.', ['%name' => $account->getAccountName()]));
$this->loggedInUser = $account;
$this->container->get('current_user')->setAccount($account);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment