/[drupal]/contributions/modules/logintoboggan/logintoboggan.module
ViewVC logotype

Contents of /contributions/modules/logintoboggan/logintoboggan.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.176 - (show annotations) (download) (as text)
Mon Nov 2 22:55:47 2009 UTC (3 weeks, 2 days ago) by thehunmonkgroup
Branch: MAIN
Changes since 1.175: +2 -2 lines
File MIME type: text/x-php
better check for no password.
1 <?php
2 // $Id: logintoboggan.module,v 1.175 2009/11/02 22:15:35 thehunmonkgroup Exp $
3
4 /**
5 * @file
6 * Logintoboggan Module
7 *
8 * This module enhances the configuration abilities of Drupal's default login system.
9 */
10
11 /**
12 * @todo
13 *
14 */
15
16 /**
17 * @wishlist
18 *
19 */
20
21 /**
22 * @defgroup logintoboggan_core Core drupal hooks
23 */
24
25 /**
26 * Implement hook_cron().
27 */
28 function logintoboggan_cron() {
29 // If set password is enabled, and a purge interval is set, check for
30 // unvalidated users to purge.
31 if (($purge_interval = variable_get('logintoboggan_purge_unvalidated_user_interval', 0)) && !variable_get('user_email_verification', TRUE)) {
32 $validating_id = logintoboggan_validating_id();
33 // As a safety check, make sure that we have a non-core role as the
34 // pre-auth role -- otherwise skip.
35 if (!in_array($validating_id, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
36 $purge_time = REQUEST_TIME - $purge_interval;
37 $accounts = db_query("SELECT u.uid, u.name FROM {users} u INNER JOIN {users_roles} ur ON u.uid = ur.uid WHERE ur.rid = :rid AND u.created < :created", array(
38 ':rid' => $validating_id,
39 ':created' => $purge_time,
40 ));
41
42 $purged_users = array();
43 // Delete the users from the system.
44 foreach ($accounts as $account) {
45 user_cancel(array(), $account->uid, 'user_cancel_delete');
46 $purged_users[] = check_plain($account->name);
47 }
48
49 // Log the purged users.
50 if (!empty($purged_users)) {
51 batch_process(drupal_get_destination());
52 watchdog('logintoboggan', 'Purged the following unvalidated users: !purged_users', array('!purged_users' => theme('item_list', array('items' => $purged_users))));
53 }
54 }
55 }
56 }
57
58 /**
59 * Implement hook_help().
60 */
61 function logintoboggan_help($path, $arg) {
62 switch ($path) {
63 case 'admin/help#logintoboggan':
64 $output = t("<p>The Login Toboggan module improves the Drupal login system by offering the following features:
65 <ol>
66 <li>Allow users to login using either their username OR their e-mail address.</li>
67 <li>Allow users to login immediately.</li>
68 <li>Provide a login form on Access Denied pages for non-logged-in (anonymous) users.</li>
69 <li>The module provides two login block options: One uses JavaScript to display the form within the block immediately upon clicking 'log in'. The other brings the user to a separate page, but returns the user to their original page upon login.</li>
70 <li>Customize the registration form with two e-mail fields to ensure accuracy.</li>
71 <li>Optionally redirect the user to a specific page when using the 'Immediate login' feature.</li>
72 <li>Optionally redirect the user to a specific page upon validation of their e-mail address.</li>
73 <li>Optionally display a user message indicating a successful login.</li>
74 <li>Optionally have unvalidated users purged from the system at a pre-defined interval (please read the CAVEATS section of INSTALL.txt for important information on configuring this feature!).</li>
75 </ol>
76 These features may be turned on or off in the Login Toboggan <a href=\"!url\">settings</a>.</p>
77 <p>Because this module completely reorients the Drupal login process you will probably want to edit the welcome e-mail on the <a href=\"!user_settings\">user settings</a> page. For instance if you have enabled the 'Set password' option, you probably should not send the user's password out in the welcome e-mail (also note when the 'Set password' option is 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:</p>
78 ", array('!url' => url('admin/config/system/logintoboggan'), '!user_settings' => url('admin/config/people/accounts')));
79 $output .= drupal_render(drupal_get_form('logintoboggan_example_help'));
80 $output .= t("<p>Note that if you have set the 'Visitors can create accounts but administrator approval is required' option for account approval, and are also using the 'Set password' feature of LoginToboggan, the user will immediately receive the permissions of the pre-authorized user role -- you may wish to create a pre-authorized role with the exact same permissions as the anonymous user if you wish the newly created user to only have anonymous permissions.</p><p>In order for a site administrator to unblock a user who is awaiting administrator approval, they must either click on the validation link they receive in their notification e-mail, or manually remove the user from the site's pre-authorized role -- afterwards the user will then receive 'authenticated user' permissions. In either case, the user will receive an account activated e-mail if it's enabled on the user settings page -- it's recommended that you edit the default text of the activation e-mail to match LoginToboggan's workflow as described. </p><p>If you are using the 'Visitors can create accounts and no administrator approval is required' option, removal of the pre-authorized role will happen automatically when the user validates their account via e-mail.</p><p>Also be aware that LoginToboggan only affects registrations initiated by users--any user account created by an administrator will not use any LoginToboggan functionality.");
81 return $output;
82 break;
83 case 'admin/config/system/logintoboggan':
84 if (module_exists('help')) {
85 $help_text = t("More help can be found at <a href=\"!help\">LoginToboggan help</a>.", array('!help' => url('admin/help/logintoboggan')));
86 }
87 else {
88 $help_text = '';
89 }
90 $output = t("<p>Customize your login and registration system. $help_text</p>");
91
92 return $output;
93 }
94 }
95
96 /**
97 * Helper function for example user e-mail textfield.
98 */
99 function logintoboggan_example_help() {
100 $example = t('
101 [user:name],
102
103 Thank you for registering.
104
105 IMPORTANT:
106 For full site access, you will need to click on this link or copy and paste it in your browser:
107
108 [logintoboggan-validation:url]
109
110 This will verify your account and log you into the site. In the future you will be able to log in to [site:login-url] using the username and password that you created during registration.
111 ');
112 $form['foo'] = array(
113 '#type' => 'textarea',
114 '#default_value' => $example,
115 '#rows' => 15,
116 );
117
118 return $form;
119 }
120
121 /**
122 * Implement hook_form_block_admin_configure_alter().
123 *
124 * @ingroup logintoboggan_core
125 */
126 function logintoboggan_form_block_admin_configure_alter(&$form, &$form_state) {
127 if (($form['module']['#value'] == 'user') && ($form['delta']['#value'] == 'login')) {
128 $form['#submit'][] = 'logintoboggan_user_block_admin_configure_submit';
129
130 $form['block_settings']['title']['#description'] .= '<div id="logintoboggan-block-title-description">'. t('<strong>Note:</strong> Logintoboggan module is installed. If you are using one of the custom login block types below, it is recommended that you set this to <em>&lt;none&gt;</em>.') .'</div>';
131
132 $form['block_settings']['logintoboggan_login_block_type'] = array(
133 '#type' => 'radios',
134 '#title' => t('Block type'),
135 '#default_value' => variable_get('logintoboggan_login_block_type', 0),
136 '#options' => array(t('Standard'), t('Link'), t('Collapsible form')),
137 '#description' => t("'Standard' is a standard login block, 'Link' is a login link that returns the user to the original page after logging in, 'Collapsible form' is a javascript collaspible login form."),
138 );
139
140 $form['block_settings']['logintoboggan_login_block_message'] = array(
141 '#type' => 'textarea',
142 '#title' => t('Set a custom message to appear at the top of the login block'),
143 '#default_value' => variable_get('logintoboggan_login_block_message', ''),
144 );
145 }
146 }
147
148 /**
149 * Implement hook_form_logintoboggan_main_settings_alter().
150 *
151 * @ingroup logintoboggan_core
152 */
153 function logintoboggan_form_logintoboggan_main_settings_alter(&$form, &$form_state) {
154 // Ensure a valid submit array.
155 $form['#submit'] = is_array($form['#submit']) ? $form['#submit'] : array();
156 // Make this submit handler run first.
157 array_unshift($form['#submit'], 'logintoboggan_main_settings_submit');
158 }
159
160 /**
161 * Implement hook_form_user_profile_form_alter().
162 *
163 * @ingroup logintoboggan_core
164 */
165 function logintoboggan_form_user_profile_form_alter(&$form, &$form_state) {
166
167 if ($form['#user_category'] == 'account') {
168 $account = $form['#user'];
169 $form['#validate'][] = 'logintoboggan_user_edit_validate';
170
171 // User is editing their own account settings, or user admin
172 // is editing their account.
173 if ($GLOBALS['user']->uid == $account->uid || user_access('administer users')) {
174 // Display link to re-send validation e-mail.
175 // Re-validate link appears if:
176 // 1. Users can create their own password.
177 // 2. User is still in the validating role.
178 // 3. Users can create accounts without admin approval.
179 // 4. The validating role is not the authorized user role.
180 $validating_id = logintoboggan_validating_id();
181 if (!variable_get('user_email_verification', TRUE) && array_key_exists($validating_id, $account->roles) && (variable_get('user_register', 1) == 1) && ($validating_id > DRUPAL_AUTHENTICATED_RID)) {
182 $form['revalidate'] = array(
183 '#type' => 'fieldset',
184 '#title' => t('Account validation'),
185 '#weight' => -10,
186 );
187 $form['revalidate']['revalidate_link'] = array(
188 '#markup' => l(t('re-send validation e-mail'), 'toboggan/revalidate/'. $account->uid),
189 );
190 }
191 }
192
193 $id = logintoboggan_validating_id();
194 $in_pre_auth_role = in_array($id, array_keys($account->roles));
195 // Messages are only necessary for user admins, and aren't necessary if
196 // pre-auth role is authenticated user.
197 if (user_access('administer users') && isset($form['account']['roles']) && $id != DRUPAL_AUTHENTICATED_RID) {
198 // User is still in the pre-auth role, so let the admin know.
199 if ($in_pre_auth_role) {
200 // This form element is necessary as a placeholder for the user's
201 // pre-auth setting on form load. It's used to compare against the
202 // submitted form values to see if the pre-auth role has been unchecked.
203 $form['logintoboggan_pre_auth_check'] = array(
204 '#type' => 'hidden',
205 '#value' => '1',
206 );
207 if ((variable_get('user_register', 1) == 2)) {
208 $form['account']['status']['#description'] = t('If this user was created using the "Immediate Login" feature of LoginToboggan, and they are also awaiting adminstrator approval on their account, you must remove them from the site\'s pre-authorized role in the "Roles" section below, or they will not receive authenticated user permissions!');
209 }
210 $form['account']['roles']['#description'] = t("The user is assigned LoginToboggan's pre-authorized role, and is not currently receiving authenticated user permissions.");
211 }
212 // User is no longer in the pre-auth role, so remove the option to add
213 // them back.
214 else {
215 unset($form['account']['roles']['#options'][$id]);
216 }
217 }
218 }
219 }
220
221 /**
222 * Implement hook_form_user_register_alter().
223 *
224 * @ingroup logintoboggan_core
225 */
226 function logintoboggan_form_user_register_form_alter(&$form, &$form_state) {
227 // Admin created accounts are only validated by the module.
228 if (user_access('administer users')) {
229 $form['#validate'][] = 'logintoboggan_user_register_validate';
230 }
231 $mail = variable_get('logintoboggan_confirm_email_at_registration', 0);
232 $pass = !variable_get('user_email_verification', TRUE);
233
234 // Ensure a valid submit array.
235 $form['#submit'] = is_array($form['#submit']) ? $form['#submit'] : array();
236
237 // Replace core's registration function with LT's registration function.
238 // Put the LT submit handler first, so other submit handlers have a valid
239 // user to work with upon registration.
240 $key = array_search('user_register_submit', $form['#submit']);
241 if ($key !== FALSE) {
242 unset($form['#submit'][$key]);
243 }
244 array_unshift($form['#submit'],'logintoboggan_user_register_submit');
245
246 if ($mail || $pass) {
247 $form['#validate'][] = 'logintoboggan_user_register_validate';
248
249 //Display a confirm e-mail address box if option is enabled.
250 if ($mail) {
251
252 $form['conf_mail'] = array('#type' => 'textfield',
253 '#title' => t('Confirm e-mail address'),
254 '#weight' => -28,
255 '#maxlength' => 64,
256 '#description' => t('Please re-type your e-mail address to confirm it is accurate.'),
257 '#required' => TRUE,
258 );
259
260 // Weight things properly so that the order is name, mail, conf_mail, then pass
261 if (isset($form['account'])) {
262 $form['account']['#weight'] = -50; // Make sure account form group is at the top of the display.
263 $form['account']['name']['#weight'] = -30;
264 $form['account']['mail']['#weight'] = -29;
265 $form['account']['conf_mail'] = $form['conf_mail'];
266 unset($form['conf_mail']);
267 $form['account']['conf_mail']['#weight'] = -28;
268 }
269 else {
270 $form['name']['#weight'] = -30;
271 $form['mail']['#weight'] = -29;
272 }
273 }
274 if ($pass) {
275 $min_pass = variable_get('logintoboggan_minimum_password_length', 0);
276 $length = $min_pass ? t('between !min and', array('!min' => $min_pass)) : t('no more than');
277 $pass_description = t('Please choose a password for your account; it must be !length 30 characters.', array('!length' => $length));
278 if (isset($form['account'])) {
279 $form['account']['pass']['#description'] = $pass_description;
280 }
281 else {
282 $form['pass']['#description'] = $pass_description;
283 }
284 }
285 }
286 }
287
288 /**
289 * Implement hook_form_user_admin_account_alter().
290 *
291 * @ingroup logintoboggan_core
292 */
293 function logintoboggan_form_user_admin_account_alter(&$form, &$form_state) {
294 // Unset the ability to add the pre-auth role in the user admin interface.
295 $id = logintoboggan_validating_id();
296 $add = t('Add a role to the selected users');
297 if ($id != DRUPAL_AUTHENTICATED_RID && isset($form['options']['operation']['#options'][$add]["add_role-$id"])) {
298 unset($form['options']['operation']['#options'][$add]["add_role-$id"]);
299 }
300 }
301
302 /**
303 * Implement hook_form_user_pass_reset_alter().
304 *
305 * @ingroup logintoboggan_core
306 */
307 function logintoboggan_form_user_pass_reset_alter(&$form, &$form_state) {
308 // Password resets count as validating an email address, so remove the user
309 // from the pre-auth role if they are still in it. We only want to run this
310 // code when the user first hits the reset login form.
311 if (arg(5) != 'login' && ($uid = (int) arg(2))) {
312 if ($account = user_load($uid)) {
313 $id = logintoboggan_validating_id();
314 $in_pre_auth_role = in_array($id, array_keys($account->roles));
315 if ($in_pre_auth_role) {
316 _logintoboggan_process_validation($account);
317 drupal_set_message(t('You have successfully validated your e-mail address.'));
318 }
319 }
320 }
321 }
322
323 /**
324 * Implement hook_form_user_admin_permissions_alter().
325 *
326 * @ingroup logintoboggan_core
327 */
328 function logintoboggan_form_user_admin_permissions_alter(&$form, &$form_state) {
329 // If the pre-auth role isn't the auth user, then add it as a setting.
330 $id = logintoboggan_validating_id();
331 if ($id != DRUPAL_AUTHENTICATED_RID) {
332 drupal_add_js(array(
333 'LoginToboggan' => array(
334 'preAuthID' => $id,
335 ),
336 ), 'setting');
337 }
338 }
339
340 /**
341 * Implement hook_form_alter().
342 *
343 * @ingroup logintoboggan_core
344 */
345 function logintoboggan_form_alter(&$form, &$form_state, $form_id) {
346 switch ($form_id) {
347 case 'user_login':
348 case 'user_login_block':
349 // Grab the message from settings for display at the top of the login block.
350 if ($login_msg = variable_get('logintoboggan_login_block_message', '')) {
351 $form['message'] = array(
352 '#markup' => filter_xss_admin($login_msg),
353 '#weight' => -50,
354 );
355 }
356 $form['name']['#attributes']['tabindex'] = '1';
357 $form['pass']['#attributes']['tabindex'] = '2';
358 $form['submit']['#attributes']['tabindex'] = '3';
359 if (variable_get('logintoboggan_login_with_email', 0)) {
360 // Ensure a valid validate array.
361 $form['#validate'] = is_array($form['#validate']) ? $form['#validate'] : array();
362 // LT's validation function must run first.
363 array_unshift($form['#validate'],'logintoboggan_user_login_validate');
364 // Use theme functions to print the username field's textual labels.
365 $form['name']['#title'] = theme('lt_username_title', array('form_id' => $form_id));
366 $form['name']['#description'] = theme('lt_username_description', array('form_id' => $form_id));
367 // Use theme functions to print the password field's textual labels.
368 $form['pass']['#title'] = theme('lt_password_title', array('form_id' => $form_id));
369 $form['pass']['#description'] = theme('lt_password_description', array('form_id' => $form_id));
370 }
371
372 if (($form_id == 'user_login_block')) {
373 $block_type = variable_get('logintoboggan_login_block_type', 0);
374 if ($block_type == 1) {
375 // What would really be nice here is to start with a clean form, but
376 // we can't really do that, because drupal_prepare_form() has already
377 // been run, and we can't run it again in the _alter() hook, or we'll
378 // get into and endless loop. Since we don't know exactly what's in
379 // the form, strip out all regular form elements and the handlers.
380 foreach (element_children($form) as $element) {
381 unset($form[$element]);
382 }
383 unset($form['#validate'], $form['#submit']);
384 $form['logintoboggan_login_link'] = array(
385 '#markup' => l(theme('lt_login_link'), 'user/login', array('query' => drupal_get_destination())),
386 );
387 }
388 elseif ($block_type == 2) {
389 $form = _logintoboggan_toggleboggan($form);
390 }
391 }
392 break;
393 }
394 }
395
396 /**
397 * Implement hook_js_alter().
398 */
399 function logintoboggan_js_alter(&$javascript) {
400 // Look for the user permissions js.
401 if (isset($javascript['modules/user/user.permissions.js'])) {
402 $id = logintoboggan_validating_id();
403 // If the pre-auth user isn't the auth user, then swap out core's user
404 // permissions js with LT's custom implementation. This is necessary to
405 // prevent the pre-auth role's checkboxes from being automatically disabled
406 // when the auth user's checkboxes are checked.
407 if ($id != DRUPAL_AUTHENTICATED_RID) {
408 $javascript['modules/user/user.permissions.js']['data'] = drupal_get_path('module', 'logintoboggan') . '/logintoboggan.permissions.js';
409 }
410 }
411 }
412
413 /**
414 * Implement hook_page_alter().
415 */
416 function logintoboggan_page_alter(&$page) {
417 // Remove blocks on access denied pages.
418 if (isset($page['#logintoboggan_denied'])) {
419 drupal_set_message(t('Access denied. You may need to login below or register to access this page.'), 'error');
420 unset($page['sidebar_first'], $page['sidebar_second']);
421 }
422 }
423
424
425
426
427 /**
428 * Implement hook_token_info().
429 */
430 function logintoboggan_token_info() {
431 $types['logintoboggan-validation'] = array(
432 'name' => t('User validation'),
433 'description' => t('Tokens related to validating user accounts.'),
434 'needs-data' => 'user',
435 );
436
437 $user['url'] = array(
438 'name' => t('Validating URL'),
439 'description' => t("A special URL the user can use to validate their account."),
440 );
441
442 return array(
443 'types' => array('logintoboggan' => $types),
444 'tokens' => array('logintoboggan' => $user),
445 );
446 }
447
448 /**
449 * Implement hook_tokens().
450 */
451 function logintoboggan_tokens($type, $tokens, array $data = array(), array $options = array()) {
452
453 $url_options = array('absolute' => TRUE);
454 if (isset($options['language'])) {
455 $url_options['language'] = $options['language'];
456 $language_code = $options['language']->language;
457 }
458 else {
459 $language_code = NULL;
460 }
461 $sanitize = !empty($options['sanitize']);
462
463 $replacements = array();
464
465 if ($type == 'logintoboggan-validation' && !empty($data['user'])) {
466 $account = $data['user'];
467 foreach ($tokens as $name => $original) {
468 switch ($name) {
469 // Validating URL.
470 case 'url':
471 $replacements[$original] = variable_get('user_register', 1) == 1 ? logintoboggan_eml_validate_url($account, $url_options) : NULL;
472 break;
473 }
474 }
475 }
476
477 return $replacements;
478 }
479
480
481
482
483 /**
484 * Custom submit function for user registration form
485 *
486 * @ingroup logintoboggan_form
487 */
488 function logintoboggan_user_register_submit($form, &$form_state) {
489
490 $reg_pass_set = !variable_get('user_email_verification', TRUE);
491
492 // Test here for a valid pre-auth -- if the pre-auth is set to the auth user, we
493 // handle things a bit differently.
494 $pre_auth = logintoboggan_validating_id() != DRUPAL_AUTHENTICATED_RID;
495
496 // If we are allowing user selected passwords then skip the auto-generate function
497 // The new user's status should default to the site settings, unless reg_passwd_set == 1
498 // (immediate login, we are going to assign a pre-auth role), and we want to allow
499 // admin approval accounts access to the site.
500 if ($reg_pass_set) {
501 $pass = $form_state['values']['pass'];
502 $status = 1;
503 }
504 else {
505 $pass = user_password();
506 $status = variable_get('user_register', 1) == 1;
507 }
508
509 // The unset below is needed to prevent these form values from being saved as
510 // user data.
511 form_state_values_clean($form_state);
512
513 // Must unset mail confirmation to prevent it from being saved in the user table's 'data' field.
514 if (isset($form_state['values']['conf_mail'])) {
515 unset($form_state['values']['conf_mail']);
516 }
517
518 // Set the roles for the new user -- add the pre-auth role if they can pick their own password,
519 // and the pre-auth role isn't anon or auth user.
520 $validating_id = logintoboggan_validating_id();
521 $roles = isset($form_state['values']['roles']) ? array_filter($form_state['values']['roles']) : array();
522 if ($reg_pass_set && ($validating_id > DRUPAL_AUTHENTICATED_RID)) {
523 $roles[$validating_id] = 1;
524 }
525
526 $form_state['values']['pass'] = $pass;
527 $form_state['values']['init'] = $form_state['values']['mail'];
528 $form_state['values']['roles'] = $roles;
529 $form_state['values']['status'] = $status;
530
531 $account = $form['#user'];
532 $account = user_save($account, $form_state['values']);
533 // Terminate if an error occurred during user_save().
534 if (!$account) {
535 drupal_set_message(t("Error saving user account."), 'error');
536 $form_state['redirect'] = '';
537 return;
538 }
539 $form_state['user'] = $account;
540
541 watchdog('user', 'New user: %name (%email).', array('%name' => $form_state['values']['name'], '%email' => $form_state['values']['mail']), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $account->uid . '/edit'));
542
543 // Add plain text password into user account to generate mail tokens.
544 $account->password = $pass;
545
546 // Compose the appropriate user message--admin approvals don't require a validation email.
547 if($reg_pass_set && variable_get('user_register', 1) == 1) {
548 if ($pre_auth) {
549 $message = t('A validation e-mail has been sent to your e-mail address. In order to gain full access to the site, you will need to follow the instructions in that message.');
550 }
551 else {
552 $message = t('Further instructions have been sent to your e-mail address.');
553 }
554 } else {
555 $message = t('Your password and further instructions have been sent to your e-mail address.');
556 }
557
558 if (variable_get('user_register', 1) == 1) {
559
560 // Create new user account, no administrator approval required.
561 $mailkey = 'register_no_approval_required';
562
563 } elseif (variable_get('user_register', 1) == 2) {
564
565 // Create new user account, administrator approval required.
566 $mailkey = 'register_pending_approval';
567
568 $message = t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.<br />Once it has been approved, you will receive an e-mail containing further instructions.');
569 }
570
571 // Mail the user.
572 _user_mail_notify($mailkey, $account);
573
574 drupal_set_message($message);
575
576 // where do we need to redirect after registration?
577 $redirect = _logintoboggan_process_redirect(variable_get('logintoboggan_redirect_on_register', ''), $account);
578
579 // Log the user in if they created the account and immediate login is enabled.
580 if($reg_pass_set && variable_get('logintoboggan_immediate_login_on_register', TRUE)) {
581 $form_state['redirect'] = logintoboggan_process_login($account, $form_state['values'], $redirect);
582 }
583 else {
584 // Redirect to the appropriate page.
585 $form_state['redirect'] = $redirect;
586 }
587 }
588
589 /**
590 * Custom validation for user login form
591 *
592 * @ingroup logintoboggan_form
593 */
594 function logintoboggan_user_login_validate($form, &$form_state) {
595 if (isset($form_state['values']['name']) && $form_state['values']['name']) {
596 if ($name = db_query("SELECT name FROM {users} WHERE LOWER(mail) = LOWER(:name)", array(
597 ':name' => $form_state['values']['name'],
598 ))->fetchField()) {
599 form_set_value($form['name'], $name, $form_state);
600 }
601 }
602 }
603
604 /**
605 * Custom validation function for user registration form
606 *
607 * @ingroup logintoboggan_form
608 */
609 function logintoboggan_user_register_validate($form, &$form_state) {
610 //Check to see whether our e-mail address matches the confirm address if enabled.
611 if (variable_get('logintoboggan_confirm_email_at_registration', 0) && isset($form_state['values']['conf_mail'])) {
612 if ($form_state['values']['mail'] != $form_state['values']['conf_mail']) {
613 form_set_error('conf_mail', t('Your e-mail address and confirmed e-mail address must match.'));
614 }
615 }
616
617 //Do some password validation if password selection is enabled.
618 if (!variable_get('user_email_verification', TRUE)) {
619 $pass_err = logintoboggan_validate_pass($form_state['values']['pass']);
620 if ($pass_err) {
621 form_set_error('pass', $pass_err);
622 }
623 }
624 }
625
626 /**
627 * Custom validation function for user edit form
628 *
629 * @ingroup logintoboggan_form
630 */
631 function logintoboggan_user_edit_validate($form, &$form_state) {
632
633 $account = $form['#user'];
634 $edit = $form_state['values'];
635
636 // If login with mail is enabled...
637 if (variable_get('logintoboggan_login_with_email', 0)) {
638 $uid = isset($account->uid) ? $account->uid : 0;
639 // Check that no user is using this name for their email address.
640 if (isset($edit['name']) && db_query("SELECT uid FROM {users} WHERE LOWER(mail) = LOWER(:mail) AND uid <> :uid", array(
641 ':mail' => $edit['name'],
642 ':uid' => $uid,
643 ))->fetchField()) {
644 form_set_error('name', t('This name has already been taken by another user.'));
645 }
646 // Check that no user is using this email address for their name.
647 if (isset($edit['mail']) && db_query("SELECT uid FROM {users} WHERE LOWER(name) = LOWER(:name) AND uid <> :uid", array(
648 ':name' => $edit['mail'],
649 ':uid' => $uid,
650 ))->fetchField()) {
651 form_set_error('mail', t('This e-mail has already been taken by another user.'));
652 }
653 }
654
655 if (!empty($edit['pass'])) {
656 // if we're changing the password, validate it
657 $pass_err = logintoboggan_validate_pass($edit['pass']);
658 if ($pass_err) {
659 form_set_error('pass', $pass_err);
660 }
661 }
662 }
663
664 /**
665 * Implement hook_init()
666 *
667 * @ingroup logintoboggan_core
668 */
669 function logintoboggan_init() {
670 global $user;
671
672 // Make sure any user with pre-auth role doesn't have authenticated user role
673 _logintoboggan_user_roles_alter($user);
674
675 // Add custom css.
676 drupal_add_css(drupal_get_path('module', 'logintoboggan') .'/logintoboggan.css');
677 }
678
679 /**
680 * Alter user roles for loaded user account.
681 *
682 * If user is not an anonymous user, and the user has the pre-auth role, and the pre-auth role
683 * isn't also the auth role, then unset the auth role for this user--they haven't validated yet.
684 *
685 * This alteration is required because sess_read() and user_load() automatically set the
686 * authenticated user role for all non-anonymous users (see http://drupal.org/node/92361).
687 *
688 * @param &$account
689 * User account to have roles adjusted.
690 */
691 function _logintoboggan_user_roles_alter($account) {
692 $id = logintoboggan_validating_id();
693 $in_pre_auth_role = in_array($id, array_keys($account->roles));
694 if ($account->uid && $in_pre_auth_role) {
695 if ($id != DRUPAL_AUTHENTICATED_RID) {
696 unset($account->roles[DRUPAL_AUTHENTICATED_RID]);
697 // Reset the permissions cache.
698 drupal_static_reset('user_access');
699 }
700 }
701 }
702
703 /**
704 * Implement hook_menu()
705 *
706 * @ingroup logintoboggan_core
707 */
708 function logintoboggan_menu() {
709 $items = array();
710
711 // Settings page.
712 $items['admin/config/system/logintoboggan'] = array(
713 'title' => 'LoginToboggan',
714 'description' => 'Set up custom login options like instant login, login redirects, pre-authorized validation roles, etc.',
715 'page callback' => 'drupal_get_form',
716 'page arguments' => array('logintoboggan_main_settings'),
717 'access callback' => 'user_access',
718 'access arguments' => array('administer site configuration'),
719 );
720
721 // Callback for user validate routine.
722 $items['user/validate'] = array(
723 'title' => 'Validate e-mail address',
724 'page callback' => 'logintoboggan_validate_email',
725 'access callback' => TRUE,
726 'type' => MENU_CALLBACK,
727 );
728
729 // Callback for handling access denied redirection.
730 $items['toboggan/denied'] = array(
731 'access callback' => TRUE,
732 'page callback' => 'logintoboggan_denied',
733 'title' => 'Access denied',
734 'type' => MENU_CALLBACK,
735 );
736
737 //callback for re-sending validation e-mail
738 $items['toboggan/revalidate'] = array(
739 'title' => 'Re-send validation e-mail',
740 'page callback' => 'logintoboggan_resend_validation',
741 'page arguments' => array(2),
742 'access callback' => 'logintoboggan_revalidate_access',
743 'access arguments' => array(2),
744 'type' => MENU_CALLBACK,
745 );
746
747 return $items;
748 }
749
750 /**
751 * Access check for user revalidation.
752 */
753 function logintoboggan_revalidate_access($uid) {
754 return $GLOBALS['user']->uid && ($GLOBALS['user']->uid == $uid || user_access('administer users'));
755 }
756
757 /**
758 * Implemenation of hook_theme().
759 *
760 * @ingroup logintoboggan_core
761 */
762 function logintoboggan_theme($existing, $type, $theme, $path) {
763 return array(
764 'lt_username_title' => array(
765 'variables' => array('form_id' => NULL),
766 ),
767 'lt_username_description' => array(
768 'variables' => array('form_id' => NULL),
769 ),
770 'lt_password_title' => array(
771 'variables' => array('form_id' => NULL),
772 ),
773 'lt_password_description' => array(
774 'variables' => array('form_id' => NULL),
775 ),
776 'lt_access_denied' => array(
777 'variables' => array(),
778 ),
779 'lt_loggedinblock' => array(
780 'variables' => array('account' => NULL),
781 ),
782 'lt_login_link' => array(
783 'variables' => array(),
784 ),
785 'lt_login_successful_message' => array(
786 'variables' => array('account' => NULL),
787 ),
788 );
789 }
790
791 /**
792 * @defgroup logintoboggan_block Functions for LoginToboggan blocks.
793 */
794
795 function logintoboggan_user_block_admin_configure_submit($form, &$form_state) {
796 variable_set('logintoboggan_login_block_type', $form_state['values']['logintoboggan_login_block_type']);
797 variable_set('logintoboggan_login_block_message', $form_state['values']['logintoboggan_login_block_message']);
798 }
799
800 /**
801 * Implement hook_block_view().
802 *
803 * @ingroup logintoboggan_core
804 */
805 function logintoboggan_block_view($delta = '') {
806 global $user;
807
808 $block = array();
809 switch ($delta) {
810 case 'logintoboggan_logged_in':
811 if ($user->uid) {
812 $block['content'] = array(
813 '#theme' => 'lt_loggedinblock',
814 '#account' => $user,
815 );
816 }
817 break;
818 }
819 return $block;
820 }
821
822 /**
823 * Implement hook_block_info().
824 *
825 * @ingroup logintoboggan_core
826 */
827 function logintoboggan_block_info() {
828 $blocks = array();
829 $blocks['logintoboggan_logged_in'] = array(
830 'info' => t('LoginToboggan logged in block'),
831 'cache' => DRUPAL_NO_CACHE,
832 );
833 return $blocks;
834 }
835
836 /**
837 * User login block with JavaScript to expand
838 *
839 * this should really be themed
840 *
841 * @return array
842 * the reconstituted user login block
843 */
844
845 function _logintoboggan_toggleboggan ($form) {
846
847 drupal_add_js(drupal_get_path('module', 'logintoboggan') .'/logintoboggan.js');
848
849 $pre = '<div id="toboggan-container" class="toboggan-container">';
850 $options = array(
851 'attributes' => array(
852 'id' => 'toboggan-login-link',
853 'class' => array('toboggan-login-link'),
854 ),
855 'query' => drupal_get_destination(),
856 );
857 $pre .= '<div id="toboggan-login-link-container" class="toboggan-login-link-container">';
858 $pre .= l(theme('lt_login_link'), 'user/login', $options);
859 $pre .= '</div>';
860
861 //the block that will be toggled
862 $pre .= '<div id="toboggan-login" class="user-login-block">';
863
864 $form['pre'] = array('#markup' => $pre, '#weight' => -300);
865 $form['post'] = array('#markup' => '</div></div>', '#weight' => 300);
866 return $form;
867 }
868
869 function logintoboggan_main_settings(&$form_state) {
870
871 $_disabled = t('disabled');
872 $_enabled = t('enabled');
873
874 $form['login'] = array(
875 '#type' => 'fieldset',
876 '#title' => t('Login'),
877 );
878
879 $form['login']['logintoboggan_login_with_email'] = array(
880 '#type' => 'radios',
881 '#title' => t('Allow users to login using their e-mail address'),
882 '#default_value' => variable_get('logintoboggan_login_with_email', 0),
883 '#options' => array($_disabled, $_enabled),
884 '#description' => t('Users will be able to enter EITHER their username OR their e-mail address to log in.'),
885 );
886
887 $form['registration'] = array(
888 '#type' => 'fieldset',
889 '#title' => t('Registration'),
890 );
891
892 $form['registration']['logintoboggan_confirm_email_at_registration'] = array(
893 '#type' => 'radios',
894 '#title' => t('Use two e-mail fields on registration form'),
895 '#default_value' => variable_get('logintoboggan_confirm_email_at_registration', 0),
896 '#options' => array($_disabled, $_enabled),
897 '#description' => 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.'),
898 );
899
900 if (module_exists('help')) {
901 $help_text = t(" More help in writing the e-mail message can be found at <a href=\"!help\">LoginToboggan help</a>.", array('!help' => url('admin/help/logintoboggan')));
902 }
903 else {
904 $help_text = '';
905 }
906 $form['registration']['logintoboggan_user_email_verification'] = array(
907 '#type' => 'checkbox',
908 '#title' => t('Set password'),
909 '#default_value' => !variable_get('user_email_verification', TRUE) ? 1 : 0,
910 '#description' => t("This will allow users to choose their initial password when registering (note that this setting is a mirror of the <a href=\"!settings\">Require e-mail verification when a visitor creates an account</a> setting, and is merely here for convenience). If selected, users will be assigned to the role below. 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. <br />NOTE: If you enable this feature, you should edit the <a href=\"!settings\">Welcome (no approval required)</a> text.", array('!settings' => url('admin/config/people/accounts'))) . $help_text,
911 );
912
913 // Grab the roles that can be used for pre-auth. Remove the anon role, as it's not a valid choice.
914 $roles = user_roles(TRUE);
915
916 $form ['registration']['logintoboggan_pre_auth_role'] = array(
917 '#type' => 'select',
918 '#title' => t('Non-authenticated role'),
919 '#options' => $roles,
920 '#default_value' => variable_get('logintoboggan_pre_auth_role', DRUPAL_AUTHENTICATED_RID),
921 '#description' => t('If "Set password" is selected, users will be able to login before their e-mail address has been authenticated. Therefore, you must choose a role for new non-authenticated users -- you may wish to <a href="!url">add a new role</a> for this purpose. Users will be removed from this role and assigned to the "authenticated user" role once they follow the link in their welcome e-mail. <strong>WARNING: changing this setting after initial site setup can cause undesirable results, including unintended deletion of users -- change with extreme caution!</strong>', array('!url' => url('admin/config/people/roles'))),
922 );
923
924 $purge_options = array(
925 0 => t('Never delete'),
926 86400 => t('1 Day'),
927 172800 => t('2 Days'),
928 259200 => t('3 Days'),
929 345600 => t('4 Days'),
930 432000 => t('5 Days'),
931 518400 => t('6 Days'),
932 604800 => t('1 Week'),
933 1209600 => t('2 Weeks'),
934 2592000 => t('1 Month'),
935 7776000 => t('3 Months'),
936 15379200 => t('6 Months'),
937 30758400 => t('1 Year'),
938 );
939
940 $form['registration']['logintoboggan_purge_unvalidated_user_interval'] = array(
941 '#type' => 'select',
942 '#title' => t('Delete unvalidated users after'),
943 '#options' => $purge_options,
944 '#default_value' => variable_get('logintoboggan_purge_unvalidated_user_interval', 0),
945 '#description' => t("If enabled, users that are still in the 'Non-authenticated role' set above will be deleted automatically from the system, if the set time interval since their initial account creation has passed. This can be used to automatically purge spambot registrations. Note: this requires cron, and also requires that the 'Set password' option above is enabled. <strong>WARNING: changing this setting after initial site setup can cause undesirable results, including unintended deletion of users -- change with extreme caution! (please read the CAVEATS section of INSTALL.txt for important information on configuring this feature)</strong>")
946 );
947
948 $form['registration']['logintoboggan_immediate_login_on_register'] = array(
949 '#type' => 'checkbox',
950 '#title' => t('Immediate login'),
951 '#default_value' => variable_get('logintoboggan_immediate_login_on_register', TRUE),
952 '#description' => t("If set, the user will be logged in immediately after registering. Note this only applies if the 'Set password' option above is enabled."),
953 );
954
955 $form['registration']['redirect'] = array(
956 '#type' => 'fieldset',
957 '#title' => t('Redirections'),
958 '#collapsible' => true,
959 '#collapsed' => false,
960 );
961
962 $form['registration']['redirect']['logintoboggan_redirect_on_register'] = array(
963 '#type' => 'textfield',
964 '#title' => t('Redirect path on Registration'),
965 '#default_value' => variable_get('logintoboggan_redirect_on_register', ''),
966 '#description' => t('Normally, after a user registers a new account, they will be taken to the front page, or to their user page if you specify <cite>Immediate login</cite> above. Leave this setting blank if you wish to keep the default behavior. If you wish the user to go to a page of your choosing, then enter the path for it here. For instance, you may redirect them to a static page such as <cite>node/35</cite>, or to the <cite>&lt;front&gt;</cite> page. You may also use <em>%uid</em> as a variable, and the user\'s user ID will be substituted in the path.'),
967 );
968
969 $form['registration']['redirect']['logintoboggan_redirect_on_confirm'] = array(
970 '#type' => 'textfield',
971 '#title' => t('Redirect path on Confirmation'),
972 '#default_value' => variable_get('logintoboggan_redirect_on_confirm', ''),
973 '#description' => t('Normally, after a user confirms their new account, they will be taken to their user page. Leave this setting blank if you wish to keep the default behavior. If you wish the user to go to a page of your choosing, then enter the path for it here. For instance, you may redirect them to a static page such as <cite>node/35</cite>, or to the <cite>&lt;front&gt;</cite> page. You may also use <em>%uid</em> as a variable, and the user\'s user ID will be substituted in the path. In the case where users are not creating their own passwords, it is suggested to use <cite>user/%uid/edit</cite> here, so the user may set their password immediately after validating their account.'),
974 );
975 $form['registration']['redirect']['logintoboggan_override_destination_parameter'] = array(
976 '#type' => 'checkbox',
977 '#title' => t('Override destination parameter'),
978 '#default_value' => variable_get('logintoboggan_override_destination_parameter', 1),
979 '#description' => t("Normally, when a Drupal redirect is performed, priority is given to the 'destination' parameter from the originating URL. With this setting enabled, LoginToboggan will attempt to override this behavior with any values set above."),
980 );
981
982 $form['other'] = array('#type' => 'fieldset',
983 '#title' => t('Other'),
984 '#tree' => FALSE,
985 );
986
987 $site403 = variable_get('site_403', '');
988 if ($site403 == '') {
989 $disabled = $default = '0';
990 }
991 elseif ($site403 == 'toboggan/denied') {
992 $disabled = '0';
993 $default = 'toboggan/denied';
994 }
995 else {
996 $disabled = $default = $site403;
997 }
998 $options = array($disabled => $_disabled, 'toboggan/denied' => $_enabled);
999
1000 $form['other']['logintoboggan_site_403'] = array(
1001 '#type' => 'radios',
1002 '#title' => t('Present login form on access denied (403)'),
1003 '#options' => $options,
1004 '#default_value' => $default,
1005 '#description' => t('Anonymous users will be presented with a login form along with an access denied message.')
1006 );
1007 $form['other']['logintoboggan_login_successful_message'] = array(
1008 '#type' => 'radios',
1009 '#title' => t('Display login successful message'),
1010 '#options' => array($_disabled, $_enabled),
1011 '#default_value' => variable_get('logintoboggan_login_successful_message', 0),
1012 '#description' => t('If enabled, users will receive a \'Login successful\' message upon login.')
1013 );
1014 $min_pass_options = array(t('None'));
1015 for ($i = 2; $i < 30; $i++) {
1016 $min_pass_options[$i] = $i;
1017 }
1018 $form['other']['logintoboggan_minimum_password_length'] = array(
1019 '#type' => 'select',
1020 '#title' => t('Minimum password length'),
1021 '#options' => $min_pass_options,
1022 '#default_value' => variable_get('logintoboggan_minimum_password_length', 0),
1023 '#description' => t('LoginToboggan automatically performs basic password validation for illegal characters. If you would additionally like to have a mimimum password length requirement, select the length here, or set to \'None\' for no password length validation.')
1024 );
1025
1026 return system_settings_form($form);
1027 }
1028
1029 function logintoboggan_denied() {
1030 if ($GLOBALS['user']->uid == 0) {
1031 // Output the user login form.
1032 drupal_set_title(t('Access Denied / User Login'));
1033 $output = drupal_get_form('user_login');
1034 drupal_set_page_content($output);
1035 // Return page attributes, hide blocks.
1036 $page = element_info('page');
1037 $page['#logintoboggan_denied'] = TRUE;
1038 }
1039 else {
1040 drupal_set_title(t('Access Denied'));
1041 $page = theme('lt_access_denied');
1042 }
1043 return $page;
1044 }
1045
1046 /**
1047 * Modified version of user_validate_name
1048 * - validates user submitted passwords have a certain length and only contain letters, numbers or punctuation (graph character class in regex)
1049 */
1050 function logintoboggan_validate_pass($pass) {
1051 if (!strlen($pass)) return t('You must enter a password.');
1052 if (ereg("[^\x80-\xF7[:graph:] ]", $pass)) return t('The password contains an illegal character.');
1053 if (preg_match('/[\x{80}-\x{A0}'. // Non-printable ISO-8859-1 + NBSP
1054 '\x{AD}'. // Soft-hyphen
1055 '\x{2000}-\x{200F}'. // Various space characters
1056 '\x{2028}-\x{202F}'. // Bidirectional text overrides
1057 '\x{205F}-\x{206F}'. // Various text hinting characters
1058 '\x{FEFF}'. // Byte order mark
1059 '\x{FF01}-\x{FF60}'. // Full-width latin
1060 '\x{FFF9}-\x{FFFD}]/u', // Replacement characters
1061 $pass)) {
1062 return t('The password contains an illegal character.');
1063 }
1064 if (strlen($pass) > 30) return t('The password is too long: it must be less than 30 characters.');
1065 $min_pass_length = variable_get('logintoboggan_minimum_password_length', 0);
1066 if ($min_pass_length && strlen($pass) < $min_pass_length) return t("The password is too short: it must be at least %min_length characters.", array('%min_length' => $min_pass_length));
1067 }
1068
1069
1070 /**
1071 * Modified version of $DRUPAL_AUTHENTICATED_RID
1072 * - gets the role id for the "validating" user role.
1073 */
1074 function logintoboggan_validating_id() {
1075 return variable_get('logintoboggan_pre_auth_role', DRUPAL_AUTHENTICATED_RID);
1076 }
1077
1078
1079 /**
1080 * Menu callback; process validate the e-mail address as a one time URL,
1081 * and redirects to the user page on success.
1082 */
1083 function logintoboggan_validate_email($uid, $timestamp, $hashed_pass, $action = 'login') {
1084
1085 $current = REQUEST_TIME;
1086 $uid = (int) $uid;
1087 // Some redundant checks for extra security
1088 if ($timestamp < $current && $uid && $account = user_load($uid) ) {
1089 // No time out for first time login.
1090 // This conditional checks that:
1091 // - the user is still in the pre-auth role or didn't set
1092 // their own password.
1093 // - the hashed password is correct.
1094 if (((variable_get('user_email_verification', TRUE) && empty($account->login)) || array_key_exists(logintoboggan_validating_id(), $account->roles)) && $hashed_pass == logintoboggan_eml_rehash($account->pass, $timestamp, $account->mail)) {
1095 watchdog('user', 'E-mail validation URL used for %name with timestamp @timestamp.', array('%name' => $account->name, '@timestamp' => $timestamp));
1096
1097 // Test here for a valid pre-auth -- if the pre-auth is set to the auth user, we
1098 // handle things a bit differently.
1099 $validating_id = logintoboggan_validating_id();
1100 $pre_auth = !variable_get('user_email_verification', TRUE) && $validating_id != DRUPAL_AUTHENTICATED_RID;
1101
1102 _logintoboggan_process_validation($account);
1103
1104 // Where do we redirect after confirming the account?
1105 $redirect = _logintoboggan_process_redirect(variable_get('logintoboggan_redirect_on_confirm', ''), $account);
1106
1107 switch ($action) {
1108 // Proceed with normal user login, as long as it's open registration and their
1109 // account hasn't been blocked.
1110 case 'login':
1111 // Only show the validated message if there's a valid pre-auth role.
1112 if ($pre_auth) {
1113 drupal_set_message(t('You have successfully validated your e-mail address.'));
1114 }
1115 if (!$account->status) {
1116 drupal_set_message(t('Your account is currently blocked -- login cancelled.'), 'error');
1117 drupal_goto('');
1118 }
1119 else {
1120 $edit = array();
1121 $redirect = logintoboggan_process_login($account, $edit, $redirect);
1122 call_user_func_array('drupal_goto', $redirect);
1123 }
1124 break;
1125 // Admin validation.
1126 case 'admin':
1127 // TODO: is this still necessary?
1128 // user has new permissions, so we clear their menu cache
1129 cache_clear_all($account->uid .':', 'cache_menu', TRUE);
1130
1131 if ($pre_auth) {
1132 // Mail the user, letting them know their account now has auth user perms.
1133 _user_mail_notify('status_activated', $account);
1134 }
1135
1136 drupal_set_message(t('You have successfully validated %user.', array('%user' => $account->name)));
1137 drupal_goto("user/$account->uid/edit");
1138 break;
1139 // Catch all.
1140 default:
1141 // TODO: is this still necessary?
1142 // user has new permissions, so we clear their menu cache
1143 cache_clear_all($account->uid .':', 'cache_menu', TRUE);
1144
1145 drupal_set_message(t('You have successfully validated %user.', array('%user' => $account->name)));
1146 drupal_goto('');
1147 break;
1148 }
1149 }
1150 else {
1151 drupal_set_message(t("Sorry, you can only use your validation link once for security reasons. Please !login with your username and password instead now.", array('!login' => l(t('login'),'user/login'))),'error');
1152 }
1153 }
1154
1155 // Deny access, no more clues.
1156 // Everything will be in the watchdog's URL for the administrator to check.
1157 drupal_access_denied();
1158 }
1159
1160 function _logintoboggan_process_validation($account) {
1161 // Test here for a valid pre-auth -- if the pre-auth is set to the auth user, we