The Login Toboggan module improves the Drupal login system by offering the following features:
Feel funny about people logging in at "http://yoursite.com/toboggan/login"? (Yes, we know it\'s a silly name.) You can use the path.module\'s "url aliases" to redefine Login Toboggan\'s paths as something else (perhaps: "usr/login" or just "login").
Because this module completely reorients the Drupal login process you will probably want to edit the welcome e-mail on the user settings page. For instance if you have enabled "Set passwords during registration" you probably should not send the user\'s password out in the welcome e-mail. Also when either "Set passwords during registration" or "Immediate login" are enabled, the %login_url becomes a verification url that the user MUST visit in order to enable authenticated status. The following is an example welcome e-mail:
', array('%url' => url('admin/settings/logintoboggan'))); $example = t(' %username, Thank you for registering at %site. IMPORTANT: For full site access, you will need to click on this link or copy and paste it in your browser: %login_url This will verify your account and log you into the site. In the future you will be able to log in using the username and password that you created during registration. Your new %site membership also enables to you to login to other Drupal powered websites (e.g. http://www.drupal.org/) without registering. Just use the following Drupal ID along with the password you\'ve chosen: Drupal ID: %username@%uri_brief -- %site team'); $output .= form(form_textarea('', 'foo', $example, 60, 15)); $output .= t('Note that if you have set the "Visitors can create accounts but administrator approval is required" option for account approval, and are also allowing users to set their own password, the user will immediately receive the permissions of the pre-authorized user role--you may wish to set the pre-authorized role to "anonymous user" if you wish the newly created user to only have anonymous permissions.
When a site administrator unblocks a user who is awaiting administrator approval, they must also manually remove the user from the site\'s pre-authorized role and add the "authenticated user" role to the user (if you are using the "Visitors can create accounts and no administrator approval is required" option, this will happen automatically).
'); return $output; case 'admin/modules#description': return t("Improves Drupal's login system."); case 'admin/settings/logintoboggan': return ''. t('Customize your login and registration system. More help can be found here', array('%url' => url('admin/help/logintoboggan'))). '
'; } } /** * Implementation of hook_menu() * * @ingroup logintoboggan_core */ function logintoboggan_menu($may_cache) { global $user; $items = array(); if ($may_cache) { //local tab for login form $items[] = array('path' => 'toboggan/login', 'title' => t('login'), 'callback' => 'logintoboggan_login', 'access' => TRUE, 'type' => MENU_LOCAL_TASK, 'weight' => 1); //local tab for registration form $items[] = array('path' => 'toboggan/register', 'title' => t('register'), 'callback' => '_logintoboggan_register', 'access' => TRUE, 'type' => MENU_LOCAL_TASK, 'weight' => 2); //local tab for password request form $items[] = array('path' => 'toboggan/password', 'title' => t('request new password'), 'callback' => 'logintoboggan_pass', 'access' => $user->uid == 0, 'type' => MENU_LOCAL_TASK, 'weight' => 3); //local tab for user validate routine $items[] = array('path' => 'user/validate', 'title' => t('validate e-mail address'), 'callback' => 'logintoboggan_validate_email', 'access' => TRUE, 'type' => MENU_CALLBACK); //callback for handling access denied redirection $items[] = array('path' => 'toboggan/denied', 'title' => t('access denied'), 'callback' => 'logintoboggan_denied', 'access' => TRUE, 'type' => MENU_CALLBACK); $items[] = array('path' => 'admin/settings/logintoboggan/settings', 'access' => user_access('access administration pages'), 'title' => t('general'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items[] = array('path' => 'admin/settings/logintoboggan/remember', 'access' => user_access('access administration pages'), 'callback' => 'logintoboggan_rememberme_admin', 'title' => t('remember me'), 'type' => MENU_LOCAL_TASK, ); } else { //determine if we need to hijack the menu path--we do it if it's an anon user trying //to get to any of the user login/register/password pages. if (variable_get('toboggan_hijack', 1) && $user->uid == 0) { $arg1 = arg(1); if (arg(0) == 'user' && (!$arg1 || $arg1 == 'register' || $arg1 == 'password')) { //since we're hijacking here, we need to perform some magic on any $_GET query strings //so they're not lost. if (is_array($_GET) && count($_GET)) { $query = array(); foreach ($_GET as $key => $value) { if ($key != 'q') { $query_pieces[] = "$key=$value"; } } if (count($query_pieces)) { $query = implode('&', $query_pieces); } else { $query = NULL; } } else { $query = NULL; } //unsetting destination here, so drupal_goto will work properly--otherwise it might create an infinite loop. unset($_REQUEST['destination']); unset($_REQUEST['edit']['destination']); //redirect to the toboggan counterpart. switch ($arg1) { case 'register': drupal_goto('toboggan/register', $query); case 'password': drupal_goto('toboggan/password', $query); default: drupal_goto('toboggan/login', $query); } } } //callback for re-sending validation e-mail $items[] = array('path' => 'toboggan/revalidate', 'title' => t('re-send validation e-mail'), 'callback' => 'logintoboggan_resend_validation', 'callback arguments' => array(arg(2)), 'access' => $user->uid == arg(2), 'type' => MENU_CALLBACK); } return $items; } /** * Rewrite of user_login() * * Changes here should be better documented... * * @param string $msg * @param string $title * @return page output */ function logintoboggan_login($msg = '', $title = NULL) { global $user, $base_url; $edit = $_POST['edit']; // If we are already logged on, go to the user page instead. if ($user->uid) { drupal_goto('user'); } if (isset($edit['name'])) { if (user_deny('user', $edit['name'])) { // blocked in user administration $error = t('The username %name has been blocked.', array('%name' => theme('placeholder', $edit['name']))); } else if ($edit['pass']) { if (!$user->uid) { $user = logintoboggan_authenticate($edit['name'], trim($edit['pass'])); } if ($user->uid) { watchdog('user', t('Session opened for %name.', array('%name' => theme('placeholder', $user->name)))); // Update the user table timestamp noting user has logged in. db_query("UPDATE {users} SET changed = '%d' WHERE uid = '%s'", time(), $user->uid); user_module_invoke('login', $edit, $user); // Redirect the user to the page he logged on from. drupal_goto(); } else { if (!$error) { $error = t('Sorry. Unrecognized username or password.') .' '. l(t('Have you forgotten your password?'), 'toboggan/password'); } watchdog('user', t('Login attempt failed for %user: %error.', array('%user' => theme('placeholder', $edit['name']), '%error' => theme('placeholder', $error)))); } } } // Display error message (if any): if ($error) { drupal_set_message($error, 'error'); } // Display login form: if ($msg) { $output .= "$msg
"; } if (variable_get('rememberme_username_status', 0) > 0) { // pre-fill username for login PAGE if this is something we're remembering if ($_COOKIE['LTRM_sessid']) { $account = db_fetch_object(db_query('SELECT name FROM {logintoboggan_rememberme} ltrm WHERE sid = \'%s\'', $_COOKIE['LTRM_sessid'])); $edit['name'] = $account->name; } } if (count(user_auth_help_links()) > 0) { if (variable_get('login_with_mail', 0)) { $output .= form_textfield(t('Username or e-mail address'), 'name', $edit['name'], 30, 64, t('Enter your %s registered username or password, or an ID from one of our affiliates: %a.', array('%s' => variable_get('site_name', 'local'), '%a' => implode(', ', user_auth_help_links())))); } else { $output .= form_textfield(t('Username'), 'name', $edit['name'], 30, 64, t('Enter your %s username, or an ID from one of our affiliates: %a.', array('%s' => theme('placeholder', variable_get('site_name', 'local')), '%a' => implode(', ', user_auth_help_links())))); } } else { if (variable_get('login_with_mail', 0)) { $output .= form_textfield(t('Username or e-mail address'), 'name', $edit['name'], 30, 64, t('Enter your %s username or e-mail address.', array('%s' => variable_get('site_name', 'local')))); } else { $output .= form_textfield(t('Username'), 'name', $edit['name'], 30, 64, t('Enter your %s username.', array('%s' => variable_get('site_name', 'local')))); } } $output .= form_password(t('Password'), 'pass', $pass, 30, 64, t('Enter the password that accompanies your username.')); if (variable_get('rememberme_login_status', 0) == 1 || variable_get('rememberme_username_status', 0) == 1) { // add a rememberme checkbox $output .= form_checkbox(t('remember me'), 'rememberme', 1, TRUE, NULL, NULL, FALSE); } $output .= form_submit(t('Log in')); drupal_set_title($title ? $title : t('user account')); $output = form($output, 'post', url('toboggan/login', logintoboggan_destination())); print theme('page', $output); } //custom registration form function _logintoboggan_register() { global $user, $base_url, $base_path; $edit = $_POST['edit']; // If we are already logged on, go to the user page instead. if ($user->uid) { drupal_goto('user/'. $user->uid); } if ($edit) { if (variable_get('login_with_mail', 0)) { // check that it's not an e-mail if (valid_email_address($edit['name'])) { form_set_error('name', t('You may not use an e-mail address as your username.')); } } //Check to see whether our e-mail address matches the confirm address if enabled. if (variable_get('email_reg_confirm', 0)) { if ($edit['mail'] != $edit['conf_mail']) { form_set_error('conf_mail', t('Your e-mail address and confirmed e-mail address must match.')); } } //Do some password validation if password selection is enabled. if (variable_get('reg_passwd_set', 0)) { if ($edit['pass'] != $edit['conf_pass']) { form_set_error('conf_pass', t('Your password and confirmed password must match.')); } $pass_err = logintoboggan_validate_pass($edit['pass']); if ($pass_err) { form_set_error('conf_pass', $pass_err); } } //We're hacking q here to get around the hardcoded check for user/register in profile module's validation code $q = $_GET['q']; $_GET['q'] = 'user/register'; user_module_invoke('validate', $edit, $edit, 'account'); if (!form_get_errors()) { //unset the confirmation variables because we don't care anymore and we don't want them stored if (variable_get('reg_passwd_set', 0)) { unset($edit['conf_pass']); } if (variable_get('email_reg_confirm', 0)) { unset($edit['conf_mail']); } $from = variable_get('site_mail', ini_get('sendmail_from')); //If we are allowing user selected passwords then skip the auto-generate function if (variable_get('reg_passwd_set', 0)) { $pass = $edit['pass']; } else { $pass = user_password(); } // TODO: Is this necessary? Won't session_write() replicate this? unset($edit['session']); if (array_intersect(array_keys($edit), array('uid', 'roles', 'init', 'session', 'status'))) { watchdog('security', t('Detected malicious attempt to alter protected user fields.'), WATCHDOG_WARNING); drupal_goto('toboggan/register'); } $password_immediate = (variable_get('reg_passwd_set', 0) || variable_get('toboggan_immed_login', 0)); //If password selection is enabled put the user in the 'validating' role initially until they confirm their e-mail address, //create a %validate_url variable for use in the e-mail, and set the user message for the type of account creation. if ($password_immediate) { $account = user_save('', array_merge($edit, array('pass' => $pass, 'init' => $edit['mail'], 'roles' => array(logintoboggan_validating_id()), 'status' => (variable_get('user_register', 1) == 1 ? 1 : 0)))); $variables = array('%username' => $account->name, '%site' => variable_get('site_name', 'drupal'), '%password' => $pass, '%uri' => $base_url, '%uri_brief' => $base_path, '%mailto' => $account->mail, '%date' => format_date(time()), '%login_uri' => url('user', NULL, NULL, TRUE), '%edit_uri' => url('user/'. $account->uid .'/edit', NULL, NULL, TRUE), '%login_url' => logintoboggan_eml_validate_url($account)); $message = t('A validation e-mail has been sent to your e-mail address. You will need to follow the instructions in that message in order to gain full access to the site.'); } else { $account = user_save('', array_merge($edit, array('pass' => $pass, 'init' => $edit['mail'], 'roles' => array(_user_authenticated_id()), 'status' => (variable_get('user_register', 1) == 1 ? 1 : 0)))); $variables = array('%username' => $account->name, '%site' => variable_get('site_name', 'drupal'), '%password' => $pass, '%uri' => $base_url, '%uri_brief' => $base_path, '%mailto' => $account->mail, '%date' => format_date(time()), '%login_uri' => url('user', NULL, NULL, TRUE), '%edit_uri' => url('user/'. $account->uid .'/edit', NULL, NULL, TRUE), '%login_url' => url('user', NULL, NULL, TRUE)); $message = t('Your password and further instructions have been sent to your e-mail address.'); } watchdog('user', t('New user: %name %email.', array('%name' => theme('placeholder', $edit['name']), '%email' => theme('placeholder', '<'. $edit['mail'] .'>'))), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $account->uid .'/edit')); if ($account->status) { // Create new user account, no administrator approval required. $subject = _user_mail_text('welcome_subject', $variables); $body = _user_mail_text('welcome_body', $variables); user_mail($edit['mail'], $subject, $body, "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from"); if (variable_get('toboggan_immed_login', 0)){ drupal_set_message($message); logintoboggan_process_login($account); } } else { // Create new user account, administrator approval required. $subject = _user_mail_text('approval_subject', $variables); $body = _user_mail_text('approval_body', $variables); $message = t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.') .''. t('Note: if you have an account with one of our affiliates (%s), you may login now instead of registering.', array('%s' => $affiliates, '%login_uri' => url('user'))) .'
'; } $default = form_textfield(t('Username'), 'name', $edit['name'], 50, 64, t('Your full name or your preferred username; only letters, numbers and spaces are allowed.'), NULL, TRUE); $default .= form_textfield(t('E-mail address'), 'mail', $edit['mail'], 50, 64, t('A password and instructions will be sent to this e-mail address from %sitemail, please check your SPAM filters.', array('%sitemail' => variable_get('site_mail', ini_get('sendmail_from')))), NULL, TRUE); //Display a confirm e-mail address box if option is enabled. if (variable_get('email_reg_confirm', 0)) { $default .= form_textfield(t('Confirm e-mail address'), 'conf_mail', $edit['conf_mail'], 50, 64, t('Please re-type your e-mail address to confirm it is accurate.'), NULL, TRUE); } //Display a password and password confirm box if users can select their own passwords. if (variable_get('reg_passwd_set', 0)) { $default .= form_password(t('Password'), 'pass', $edit['pass'], 50, 30, t('Please choose a password for your account; it must be between 6 and 30 characters and spaces are not allowed.'), NULL, TRUE); $default .= form_password(t('Confirm Password'), 'conf_pass', $edit['conf_pass'], 50, 30, t('Please re-type your password to confirm it is accurate.'), NULL, TRUE); } // Profile.module checks the path, so we have to fake it out $oldget = $_GET['q']; $_GET['q'] = 'user/register'; $extra = _user_forms($edit, $account, $category, 'register'); $_GET['q'] = $oldget; // Only display form_group around default fields if there are other groups. if ($extra) { $output .= form_group(t('Account information'), $default); $output .= $extra; } else { $output .= $default; } $output .= form_submit(t('Create new account')); drupal_set_title(t('new user registration')); $form = form($output, 'post', url('toboggan/register', 'destination='.$_GET['destination'])); print theme('page', $form); } //support function which returns the custom login link function _logintoboggan_link() { global $user; //not logged in, then display the login/register link if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1))) && !(arg(0) == 'toboggan')) { return l(t('login/register'), 'toboggan/login'); //otherwise display the username you are logged in as. } elseif ($user->uid) { return theme_lt_loggedinblock(); } } /** * @defgroup logintoboggan_block Functions for LoginToboggan blocks. */ /** * Implementation of hook_block */ function logintoboggan_block($op = 'list', $delta = 0, $edit = array()) { global $user; switch ($op) { case 'list' : $blocks[0]['info'] = t('LoginToboggan custom login'); return $blocks; break; case 'configure': if ($delta == 0){ $output .= form_checkbox(t('Display the \'logged in\' block'), 'toboggan_display_logged_in', 1, variable_get('toboggan_display_logged_in', 1)); $output .= form_radios(t('Block type'), 'toboggan_block_type', variable_get('toboggan_block_type', 1), array(t('Link'), t('Collapsible Form'))); $output .= form_textarea(t('Set a custom message to appear at the top of your login block'), 'toboggan_block_msg', variable_get('toboggan_block_msg', ''), 200, 5); return $output; } break; case 'save' : if ($delta == 0){ variable_set('toboggan_block_type', $edit['toboggan_block_type']); variable_set('toboggan_display_logged_in', $edit['toboggan_display_logged_in']); variable_set('toboggan_block_msg', $edit['toboggan_block_msg']); } break; case 'view' : if (user_access('access content')) { switch ($delta) { case 0: // For usability's sake, avoid showing two login forms on one page. if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1))) && !(arg(0) == 'toboggan')) { if (variable_get('toboggan_block_type', 1) == 1) { $edit = $_POST['edit']; $block['content'] = _logintoboggan_toggleboggan($edit); } else { $block['content'] = l(t('Login/Register'), 'user/login', array(), drupal_get_destination()); } } elseif ($user->uid && variable_get('toboggan_display_logged_in', 1)) { $block['content'] = theme('lt_loggedinblock'); } } return $block; } break; } } /** * Custom theme function for defining what gets displayed for logged in users. */ function theme_lt_loggedinblock(){ global $user; return $user->name .' | ' . l(t('log out'), 'logout'); } /** * User login block with JavaScript to expand * * this should really be themed * * @param array $edit * @return string * the rendered html */ function _logintoboggan_toggleboggan ($edit = array()){ $output = 'Login Toboggan version: $Revision: 1.7.2.46 $, $Date: 2006/09/08 16:08:58 $
'); $_disabled = t('disabled'); $_enabled = t('enabled'); $group = form_radios(t('Allow users to login using their e-mail address'), 'login_with_mail', variable_get('login_with_mail', 0), array($_disabled, $_enabled), t('Users will be able to enter EITHER their username OR their e-mail address to log in. NOTE: This will disallow users from registering using an e-mail address as their username.')); $output = $version . form_group(t('Login'), $group); $group = form_radios(t('Use two e-mail fields on registration form'), 'email_reg_confirm', variable_get('email_reg_confirm', 0), array($_disabled, $_enabled), t('User will have to type the same e-mail address into both fields. This helps to confirm that they\'ve typed the correct address.')); $group .= form_radios(t('Set password during registration'), 'reg_passwd_set', variable_get('reg_passwd_set', 0), array($_disabled, $_enabled), t('This will allow users to choose their initial password when registering.')); $group .= form_radios(t('Immediate login'), 'toboggan_immed_login', variable_get('toboggan_immed_login', 0), array($_disabled, $_enabled), t('When enabled, users will be assigned to the role below and logged in immediately upon registration. They will not be assigned to the "authenticated user" role until they confirm their e-mail address by following the link in their registration e-mail. It is HIGHLY recommended that you set up a "pre-authorized" role with limited permissions for this purpose.'.t('Use the %name feature to extend this for users on your site, and/or to pre-fill their login name the next time they come to the site. Your current user login sessions will last %time DAYS.', array('%time' => round($time / 24 / 3600, 1), '%name' => t('remember me'))).'
'; if (variable_get('rememberme_login_status', 0) == 1) { $output .= ''.t('Sessions for users who do not use the %name checkbox will last %time DAYS', array('%name' => t('remember me'), '%time' => round(ini_get('session.gc_maxlifetime') / 24 / 3600, 1))).'
'; } $login_group .= form_radios( t('status for logins', array('%name' => t('remember me'))), 'login_status', variable_get('rememberme_login_status', 1), array('Off', 'On w/Checkbox', 'On Always'), t('Choose whether or not to enable the %name feature to log users in automatically, either through a checkbox on user login forms, or else as an always-on feature.', array('%name' => t('remember me'))), FALSE, NULL); $login_group .= form_textfield( t('session lifetime'), 'login_life', round(variable_get('rememberme_login_life', $time) / 24 / 3600, 1), 10, 10, t('Time in DAYS that you wish the auto-login feature to remain active. In other words: how long can a user be away from the site, and still find themselves logged when they return using the same computer.'), NULL, FALSE); $output .= form_group( t('logins'), $login_group, t('These settings control the %name function for automatically logging users in. Note that this presents a security vulnerability in that the browser the user used to access your site will be able to auto-log-in for however long you choose here!', array('%name' => t('remember me')))); $username_group .= form_radios( t('status for usernames', array('%name' => t('remember me'))), 'username_status', variable_get('rememberme_username_status', 2), array('Off', 'On w/Checkbox', 'On Always'), t('Choose whether or not to enable the %name feature to remember usernames automatically, either with a checkbox on user login forms, or else as an always-on feature.', array('%name' => t('remember me'))), FALSE, NULL); $username_group .= form_textfield( t('username remember lifetime'), 'username_life', round(variable_get('rememberme_username_life', $time * 2) / 24 / 3600, 1), 10, 10, t('Time in DAYS that you wish to remember usernames. If you are also using the login setting, this should probably be longer.'), NULL, FALSE); $output .= form_group( t('username'), $username_group, t('These settings control the %name function for remebering usernames and pre-filling the login form with that value.', array('%name' => t('remember me'))) ); $output .= form_submit('submit', 'submit', NULL); //$output .= drupal_get_form('logintoboggan_rememberme', $output); print theme('page',form($output)); } /** * Shorthand for the logic stuff for logins. * * This compares the status of the settings with the presence of the "remember * me" checkbox on the login form and tells us whether or not to remember what. */ function _logintoboggan_rememberme_login($checkbox = FALSE) { global $user; if ($_COOKIE['LTRM_sessid']) { // out with the old, just in case db_query('DELETE FROM {logintoboggan_rememberme} WHERE sid = \'%s\'', $_COOKIE['LTRM_sessid']); } if (variable_get('rememberme_login_status', 0) > 0 || variable_get('rememberme_username_status', 0) > 0) { // system is active, continue if ((variable_get('rememberme_login_status', 0) == 1 && $checkbox) || variable_get('rememberme_login_status', 0) == 2) { // we're supposed to remember the uid (auto re-login) $uid = $user->uid; } else { // store 0 for the uid, but we may stlll remember the login name for pre-fill $uid = 0; } if ((variable_get('rememberme_username_status', 0) == 1 && $checkbox) || variable_get('rememberme_username_status', 0) == 2) { // we're supposed to remember the username (prefill login form) // if we're allowing email addys, store that instead for prefill $name = variable_get('login_with_mail', 0) ? $user->mail : $user->name; } else { // store nothing for the username $name = ''; } $token = md5(time()/$user->created); // should be pretty safe _logintoboggan_rememberme_setcookie($token); db_query('INSERT INTO {logintoboggan_rememberme} (uid, name, sid, timestamp) VALUES(%d, \'%s\', \'%s\', %d)', $uid, $name, $token, time()); watchdog('user', t('Session remembered for %name.', array('%name' => theme('placeholder', $user->name)))); } } /** * Set our Logintoboggan Rememberme Session cookie (LTRM_sessid) */ function _logintoboggan_rememberme_setcookie($token) { global $base_url; $url = str_replace('http://', '', $base_url); $url = explode('/', $url); $domain = array_shift($url); $path = '/'.implode('/', $url); // get the longer lifetime for cookie life; this should generally be username, but just in case... $lifetime = ((variable_get('rememberme_username_life', 0) > (variable_get('rememberme_login_life', 0))) ? variable_get('rememberme_username_life', 0) : variable_get('rememberme_login_life', 0)); setcookie('LTRM_sessid', $token, time() + $lifetime, $path, $domain); } function logintoboggan_cron() { db_query('DELETE FROM {logintoboggan_rememberme} WHERE timestamp < %d', time() - variable_get('rememberme_username_life', 1*365*24*60*60) ); }