diff --git a/commerce_checkout_login.module b/commerce_checkout_login.module index e812f22..825ccec 100644 --- a/commerce_checkout_login.module +++ b/commerce_checkout_login.module @@ -5,6 +5,57 @@ * Adds an inline login form to the Account Information checkout pane. */ +/** + * Implements hook_form_FORM_ID_alter(). + * + * Capture checkout pane settings + */ +function commerce_checkout_login_form_commerce_checkout_pane_settings_form_alter(&$form, &$form_state, $form_id) { + // Exit if not account checkout pane + if (!isset($form['checkout_pane']['#value']['pane_id']) || $form['checkout_pane']['#value']['pane_id'] != 'account') { + return; + } + + $form['settings']['commerce_checkout_login'] = array( + '#type' => 'fieldset', + '#title' => t('Commerce checkout login configuration'), + ); + $settings_form = &$form['settings']['commerce_checkout_login']; + + $settings_form['commerce_checkout_login_username'] = array( + '#type' => 'checkbox', + '#title' => t('Allow new customers choose their username'), + '#description' => t('If checked, a new customer will have the option to choose a username.'), + '#default_value' => variable_get('commerce_checkout_login_username', FALSE), + ); + + $settings_form['commerce_checkout_login_password'] = array( + '#type' => 'checkbox', + '#title' => t('Allow new customers choose their password'), + '#description' => t('If checked, a new customer will have the option to choose a password if the system wide settings permit this.'), + '#default_value' => variable_get('commerce_checkout_login_password', FALSE), + ); + + $settings_form['commerce_checkout_login_send_welcome_message'] = array( + '#type' => 'checkbox', + '#title' => t('Send welcome e-mail after creating a new user.'), + '#description' => t('If checked, an e-mail with a welcome message (no approval required) will be sent to the new user. You can configue the message !here.', array('!here' => l(t('here'), 'admin/config/people/accounts'))), + '#default_value' => variable_get('commerce_checkout_login_send_welcome_message', FALSE), + ); + $settings_form['commerce_checkout_login_send_welcome_message_show_message'] = array( + '#type' => 'checkbox', + '#title' => t('Show an e-mail notification message.'), + '#description' => t('If checked, a system message will be set, notifying the user of the welcome e-mail being sent.'), + '#default_value' => variable_get('commerce_checkout_login_send_welcome_message_show_message', FALSE), + '#prefix' => '
', + '#suffix' => '
', + '#states' => array( + 'invisible' => array( + ':input[name="commerce_checkout_login_send_welcome_message"]' => array('checked' => FALSE), + ), + ), + ); +} /** * Implements hook_form_FORM_ID_alter(). @@ -14,6 +65,7 @@ */ function commerce_checkout_login_form_commerce_checkout_form_alter(&$form, &$form_state) { global $user; + $show_conf_mail = variable_get('commerce_order_account_pane_mail_double_entry', FALSE); // If the Account Information pane is on the current checkout page and the // user is not logged in... @@ -25,31 +77,65 @@ function commerce_checkout_login_form_commerce_checkout_form_alter(&$form, &$for ), ); + // Add ajax to confirmation mail for immediate validation: + if (isset($form['account']['login']['mail_confirm'])) { + $form['account']['login']['mail_confirm']['#ajax'] = array( + 'callback' => 'commerce_checkout_login_checkout_form_refresh', + 'wrapper' => 'account-login-container', + ); + } + // Check the form state to see if an e-mail address has been specified. - if (!empty($form_state['values']['account']['login'])) { - $mail = trim($form_state['values']['account']['login']['mail']); + if (!empty($form_state['values']['account']['login']['mail']) || !empty($form_state['order']->mail)) { + $mail = !empty($form_state['values']['account']['login']['mail']) ? trim($form_state['values']['account']['login']['mail']) : $form_state['order']->mail; // Don't attempt to load the user for an invalid e-mail address. - if ($error = user_validate_mail($mail)) { - form_set_error('account][login][mail', $error); - } - elseif ($account = user_load_by_mail($mail)) { - // If a user already exists for the given e-mail address, display a - // message letting the customer know this. - $form['account']['login']['mail']['#description'] = t('There is already an account registered to %mail. You can login now to use your account information during checkout.', array('%mail' => $mail)); - + if (!user_validate_mail($mail) && user_load_by_mail($mail)) { + // An existing e-mail address has been entered. Add a password field to + // allow the customer to login. $form['account']['login']['password'] = array( '#type' => 'password', '#title' => t('Password'), + '#required' => TRUE, + '#description' => t('Please enter the password for your account. !forgot', array('!forgot' => l(t('Did you forget your password?'), 'user/password'))) ); - $form['account']['login']['login_now'] = array( - '#type' => 'submit', - '#value' => t('Login now'), - '#limit_validation_errors' => array(array('account')), - '#validate' => array('commerce_checkout_login_checkout_form_validate'), - '#submit' => array('commerce_checkout_login_checkout_form_submit'), - ); + // No e-mail confirmation is required to login. So we hide the e-mail + // confirmation field if it is set. + if (isset($form['account']['login']['mail_confirm'])) { + $form['account']['login']['mail_confirm']['#value'] = $mail; + $form['account']['login']['mail_confirm']['#access'] = FALSE; + } + } + elseif (variable_get('commerce_checkout_login_username', FALSE) || variable_get('commerce_checkout_login_password', FALSE)) { + if (variable_get('commerce_checkout_login_username', FALSE)) { + $form['account']['login']['name'] = array( + '#type' => 'textfield', + '#title' => t('Username'), + '#maxlength' => USERNAME_MAX_LENGTH, + '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.'), + '#attributes' => array('class' => array('username')), + ); + } + + // Find out if email verification is required. + $mail_verify = variable_get('user_email_verification', TRUE); + $mail_verify = !$mail_verify ? variable_get('logintoboggan_confirm_email_at_registration', 0) : $mail_verify; + + // If password selection is enabled and verification is not required add + // the password field. + if (variable_get('commerce_checkout_login_password', FALSE) && !$mail_verify) { + $desc = t('Provide a password for the new account in both fields.'); + if ($min_pass = variable_get('logintoboggan_minimum_password_length', 0)) { + $desc .= ' ' . t('Password must be at least %length characters.', array('%length' => $min_pass)); + } + + $form['account']['login']['pass'] = array( + '#type' => 'password_confirm', + '#size' => 25, + '#description' => $desc, + ); + } } } } @@ -59,50 +145,219 @@ function commerce_checkout_login_form_commerce_checkout_form_alter(&$form, &$for * Ajax callback: returns the account information pane to an AJAX request. */ function commerce_checkout_login_checkout_form_refresh($form, $form_state) { - return $form['account']['login']; + $show_conf_mail = variable_get('commerce_order_account_pane_mail_double_entry', FALSE); + + $mail = !empty($form_state['values']['account']['login']['mail']) ? trim($form_state['values']['account']['login']['mail']) : ''; + _validate_email($mail); + + if (!$show_conf_mail) { + return $form['account']['login']; + } + + + // Return AJAX commands to set focus to the mail confirmation field for + // improved user experience. + $commands = array(); + $commands[] = ajax_command_replace('#' . $form['account']['login']['mail']['#ajax']['wrapper'], drupal_render($form['account']['login'])); + $commands[] = ajax_command_invoke('[name="account[login][mail_confirm]"]', 'focus'); + print ajax_render($commands); + exit; } /** - * Validate callback: validate the user credentials + * Implements hook_commerce_checkout_pane_info_alter(). */ -function commerce_checkout_login_checkout_form_validate($form, &$form_state) { - // @todo Add flood control support as in user_login_authenticate_validate(). - $account = user_load_by_mail($form_state['values']['account']['login']['mail']); - - // If the user account is blocked... - if (user_is_blocked($account->name)) { - // Display an appropriate error message. - form_set_error('account][login][email', t('The username %name has not been activated or is blocked.', array('%name' => $account->name))); +function commerce_checkout_login_commerce_checkout_pane_info_alter(&$checkout_panes) { + if (isset($checkout_panes['account'])) { + $checkout_panes['account']['callbacks']['checkout_form_validate'] = 'commerce_checkout_login_commerce_checkout_pane_validate'; + $checkout_panes['account']['callbacks']['checkout_form_submit'] = 'commerce_checkout_login_commerce_checkout_pane_submit'; } - elseif ($uid = user_authenticate($account->name, $form_state['values']['account']['login']['password'])) { - // Otherwise, if the user authenticates based on the name of the loaded - // account and the supplied password, retain the uid to login the user on - // final submission. - $form_state['commerce_checkout_login_uid'] = $uid; +} + +/** + * Account checkout pane validation. + */ +function commerce_checkout_login_commerce_checkout_pane_validate(&$form, &$form_state, $checkout_pane, $order) { + $mail = !empty($form_state['values']['account']['login']['mail']) ? trim($form_state['values']['account']['login']['mail']) : $form_state['order']->mail; + $validated = _validate_email($mail); + + if ($account = user_load_by_mail($mail)) { + $validated = _validate_existing_account($form, $form_state, $account); } + // A non-existing e-mail address has been entered. else { - // But indicate that we could not login the user if validation failed. - form_set_error('account][login][password', t('Sorry, unrecognized username or password. Have you forgotten your password?', array('@password' => url('user/password')))); + // Validate e-mail confirmation field if set. + if (isset($form['account']['login']['mail_confirm'])) { + $mail_confirm = !empty($form_state['values']['account']['login']['mail_confirm']) ? trim($form_state['values']['account']['login']['mail_confirm']) : ''; + if (empty($mail_confirm) || $mail != $mail_confirm) { + form_set_error('account][login][mail_confirm', t('The specified e-mail addresses do not match.')); + $validated = FALSE; + } + } + + if (isset($form['account']['login']['password'])) { + // A password field is present. During rebuild of the form, this password + // field will be removed because a non-existing email address was entered. + // Previous form validation might already have set a password required + // message. We need to remove said message. + $messages = drupal_get_messages('error'); + $password_title = $form['account']['login']['password']['#title']; + foreach ($messages['error'] as $message) { + if ($message != strip_tags(t('The field %field is required.', array('%field' => $password_title)))) { + // This message is not the password required message, re-add it. + drupal_set_message($message, 'error'); + } + } + } + + $username = substr($mail, 0, strpos($mail, '@')); + if (variable_get('commerce_checkout_login_username', FALSE)) { + $username = $form_state['values']['account']['login']['name']; + // Validate username. + if ($error = user_validate_name($username)) { + form_set_error('account][login][name', $error); + $validated = FALSE; + } + // Make sure the name is not already taken. + elseif ((bool) db_select('users')->fields('users', array('uid'))->condition('name', db_like($username), 'LIKE')->range(0, 1)->execute()->fetchField()) { + form_set_error('account][login][name', t('The name %name is already taken.', array('%name' => $username))); + $validated = FALSE; + } + } + else{ + // Make sure the automatic username does not already exist. + $similar_names = db_select('users') + ->fields('users', array('uid', 'name')) + ->condition('name', db_like($username), 'LIKE') + ->execute() + ->fetchCol(1); + if ($similar_names && in_array($username, $similar_names)) { + for ($i = 1; $i < count($similar_names) + 2; $i++) { + if (!in_array($username.$i, $similar_names)) { + $username = $username.$i; + break; + } + } + } + } + + if (variable_get('commerce_checkout_login_password', FALSE)) { + // Validate password if logintoboggan is present. + if (isset($form_state['values']['account']['login']['pass']) && module_exists('logintoboggan')) { + if ($error = logintoboggan_validate_pass($form_state['values']['account']['login']['pass'])) { + form_set_error('account][login][pass', $error); + $validated = FALSE; + } + } + } + + if ($validated) { + // Create new user. + $edit = array( + 'name' => $username, + 'mail' => $mail, + 'init' => $mail, + 'pass' => isset($form_state['values']['account']['login']['pass']) ? $form_state['values']['account']['login']['pass'] : user_password(), + 'status' => 1, + 'timezone' => variable_get('user_default_timezone', variable_get('date_default_timezone', '')), + ); + + $account = user_save(NULL, $edit); + // set the uid to enable automatic login on submit. + $form_state['commerce_checkout_login_uid'] = $account->uid; + + if (variable_get('commerce_checkout_login_send_welcome_message', FALSE)) { + _user_mail_notify('register_no_approval_required', $account); + if (variable_get('commerce_checkout_login_send_welcome_message_show_message', FALSE)) { + drupal_set_message(t('A welcome message with further instructions has been sent to your e-mail address.')); + } + } + } } + + return $validated; } /** - * Submit callback: attempt a login of the user now. + * Account checkout pane submit handler. */ -function commerce_checkout_login_checkout_form_submit($form, &$form_state) { +function commerce_checkout_login_commerce_checkout_pane_submit(&$form, &$form_state, $checkout_pane, $order) { global $user; if (!empty($form_state['commerce_checkout_login_uid'])) { - // Load the specified user into the global $user variable. - $user = user_load($form_state['commerce_checkout_login_uid']); + if (!empty($user->uid)) { + // Load the full user object + $user = user_load($user->uid); + } + else { + // Load the specified user into the global $user variable. + $user = user_load($form_state['commerce_checkout_login_uid']); - // "Finalize" the login by triggering the appropriate system messages, IP - // address and login timestamp logging, and user login hook. - user_login_finalize(); + // "Finalize" the login by triggering the appropriate system messages, IP + // address and login timestamp logging, and user login hook. + user_login_finalize(); + } // Convert the current cart order to an authenticated cart for the current // user and clear out our variable from the form state. - commerce_cart_order_convert($form_state['order'], $user); + $oid = is_object($form_state['order']) ? $form_state['order']->order_id : $form_state['order']; + $order = commerce_order_load($oid); + if (empty($order->uid)) { + commerce_cart_order_convert($order, $user); + } unset($form_state['commerce_checkout_login_uid']); } } + +/** + * Email validation helper + */ +function _validate_email($mail) { + if ($error = user_validate_mail($mail)) { + form_set_error('account][login][mail', $error); + return FALSE; + } + return TRUE; +} + +/** + * Known user validation helper. + */ +function _validate_existing_account(&$form, &$form_state, &$account) { + if ($user = user_uid_optional_load()) { + if ($user->uid === $account->uid) { + // Nothing to validate, the user is already logged in. + return TRUE; + } + } + // If there is no password field present, the customer has not yet been + // informed about the the pre-existing account. We therefore have to inform + // the customer about this. + if (!isset($form['account']['login']['password'])) { + $message = t('There is already an account registered to %mail. Please enter your password to continue checkout.', array('%mail' => $account->mail)); + drupal_set_message($message, 'warning'); + // By invalidating the form submission we trigger a rebuild which will cause + // the password field to be rendered. + return FALSE; + } + else { + // If the user account is blocked... + if (user_is_blocked($account->name)) { + // Display an appropriate error message. + form_set_error('account][login][email', t('The username %name has not been activated or is blocked.', array('%name' => $account->name))); + return FALSE; + } + + // If the user authenticates based on the name of the loaded account and the + // supplied password, retain the uid to login the user on final submission. + if ($uid = user_authenticate($account->name, $form_state['values']['account']['login']['password'])) { + $form_state['commerce_checkout_login_uid'] = $uid; + return TRUE; + } + else { + // Indicate that the user could not be logged in if validation failed. + form_set_error('account][login][password', t('Sorry, unrecognized username or password. !password', array('!password' => l('Have you forgotten your password?', 'user/password')))); + return FALSE; + } + } +}