Drupal requires that the user have cookies enabled in order to log in; a user with cookies turned off can still interact with Drupal as an anonymous user.
During the session phase of the bootstrap process, Drupal creates a global $user object that represents the identity of the
current user. If the user is not logged in (and so does not have a session cookie), then he or she is treated as an
anonymous user. The code that creates an anonymous user looks like this (and lives in includes/bootstrap.inc):
function drupal_anonymous_user($session = '') {
$user = new stdClass();
$user->uid = 0;
$user->hostname = ip_address();
$user->roles = array();
$user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
$user->session = $session;
$user->cache = 0; return $user;
}
On the other hand, if the user is currently logged in, the $user object is created by joining the users table, roles, and sessions
tables on the user’s ID. Values of all fields in both tables are placed into the $user object.
The $user object is easily inspected by adding global $user; print_r($user); to index.php. The following is what a $user object
generally looks like for a logged-in user:
stdClass Object ( [uid] => 1[name] => admin [pass] => $S$CnUvfOYdoxl/Usy.X/Y9/SCmOLLY6Qldrzjf7EOW0fR4LG7rCAmR [mail] => joe@example.com[theme] => [signature] => [signature_format] => 0 [created] => 1277957059 [access] => 1278254230 [login] => 1277990573 [status] => 1 [timezone] => [language] => [picture] => 0[init] => joe@example.com[data] =>[sid] => 8cnG9e0jsCC7I7IYwfWB0rmRozIbaLlk35IQGN5fz9k [ssid] =>[hostname] => ::1[timestamp] => 1278254231 [cache] => 0[session] => batches|a:1{i:3;b:1;} [roles] => Array
([2] => authenticated user[3] => administrator)}
In the $user object just displayed, italicized field names denote that the origin of the data is the sessions table. The components of
the $user object are explained in Table 6-1.
Component
|
Description
|
Provided by
the users Table
|
|
uid
|
The user ID of this user. This is the primary key of the users table and
is unique to this Drupal installation.
|
name
|
The user’s username, typed
by the user
when logging in.
|
pass
|
An sha512 hash of the user’s password,
which is compared when the
user logs in. Since the
actual passwords aren’t saved, they
can only be reset and not
restored.
|
mail
|
The user’s current e-mail address.
|
theme
|
This field is deprecated but left in the object for compatibility purposes.
|
signature
|
The signature the user entered on his or her account page. Used when the user adds a comment and only visible when the comment module is enabled.
|
Signature format
|
The format of the users signature (e.g., filtered text, full text)
|
created
|
A Unix timestamp of when this user
account was created.
|
access
|
A Unix timestamp denoting the user’s last access time.
|
login
|
A Unix timestamp denoting the user’s last
successful login.
|
status
|
Contains 1
if the user is in good standing or 0 if the user has been blocked.
|
timezone
|
The number of seconds that
the user’s time zone is offset from GMT.
|
language
|
The user’s default language. Empty unless
multiple languages are enabled on a site and the user
has chosen a language by editing account preferences.
|
picture
|
The path to the image file the user has associated with the account.
|
init
|
The initial e-mail
address the user provided when registering.
|
data
|
Arbitrary data can be stored here by modules (see the next
section, “Storing Data in the $user Object”).
|
Provided by the user_roles Table
|
|
roles
|
The roles currently assigned to this user.
|
Provided by the sessions Table
|
|
sid
|
The session ID assigned to this user
session by PHP.
|
Ssid
|
A secure session ID assigned to this user
session by PHP.
|
hostname
|
The IP address from which the user is viewing the
current page.
|
timestamp
|
A Unix timestamp representing time at
which the user’s
browser last received a completed
page.
|
cache
|
A timestamp used for per-user caching
(see includes/cache.inc).
|
session
|
Arbitrary,
transitory data stored for the duration of the user’s session can
be stored here by modules.
|
Testing If a User Is Logged In
During a request, the standard way of testing if a user is logged in is to test whether $user->uid is 0.
Drupal has a convenience function called user_is_logged_in() for this purpose (there is a corresponding user_is_anonymous()
function):
if (user_is_logged_in())
{
$output = t('User is logged in.');
else
{
$output = t('User is an anonymous user.');
}
}
Introduction to user hooks
Implementing user hooks gives your modules a chance to react to the different operations performed on a user account and to
modify the $user object. There are several variants of hook_user, each variant performing a specific action (see Table 6-2).
Table 6-2. hook user
Functions
|
|
Hook function
|
Purpose
|
hook_username_alter(&$name, $account)
|
Alter the username that
is displayed for the user.
|
hook_user_cancel($edit, $account, $method)
|
Act on user account cancellations.
|
hook_user_cancel_methods_alter(&$methods)
|
Modify an account cancellation method.
|
hook_user_categories()
|
Retrieve a list of user
setting
or profile information changes.
|
hook_user_delete($account)
|
Respond to user
deletion.
|
hook_user_insert(&$edit, $account, $category)
|
A user account was created.
|
hook_user_load($users)
|
Act on user objects when loaded from the database.
|
hook_user_login(&$edit, $account)
|
The user just logged
in.
|
hook_user_logout($account)
|
The user just logged out.
|
hook_user_operations()
|
Add mass user operations.
|
hook_user_presave(&$edit, $account, $category)
|
A user account is about to be created or updated.
|
hook_user_role_delete($role)
|
Inform other
modules that a user role has been deleted.
|
hook_user_role_insert($role)
|
Inform other
modules that a user role has been added.
|
hook_user_role_update($role)
|
Inform other
modules that a user role has been updated.
|
hook_user_update(&$edit, $account, $category)
|
A user account was updated.
|
hook_user_view($account, $viewmode)
|
The user’s account information is being displayed.
|
hook_user_view_alter(&$build)
|
The user was built;
the module may modify the structured content.
|
Understanding hook_user_view($account, $view_mode)
hook_user_view() is used by modules to add information to user profile pages (e.g., what you see at http://example.com/?q=user/1; see Figure 6-1).
Figure 6-1. The user profile page, with the blog module and the user module implementing
hook_user_view() to add additional information
Let’s examine how the blog module added its information to this page using the hook_user_view function:
/**
* Implements hook_user_view().
*/
function blog_user_view($account) {
if (user_access('create blog content', $account)) {
$account->content['summary' ['blog'] = array( '#type' => 'user_profile_item', '#title' => t('Blog'), '#markup' => l(t('View recent blog entries'), "blog/$account->uid", array('attributes' => array('title' => t("Read !username's latest blog entries.", array('!username' => format_username($account)))))),'#attributes' => array('class' => array('blog')),);}}
The view function stashes some information into $user->content. User profile information is organized into categories,
with each category representing a page of information about a user. In Figure 6-1, there is just one category, called History.
The outerarray should be keyed by category name. In the preceding example, the name of the key is summary, which corresponds
to the History category (admittedly, it would make more sense to name the key and the category the same thing). The interior array(s)should have a unique textual key (blog in this case) and have #type, #title, #markup, and #attributes elements. The type user_profile_item points Drupal’s theming layer to modules/user/user- profile-item.tpl.php. By comparing the code snippet with
Figure 6-1, you can see how these elements are rendered. Listing 6-1 shows the contents of the $user->content array,
which became the page shown in Figure 6-1.
Listing 6-1. The Structure of $user->content
Array ([#pre_render] => Array ([0] => _field_extra_fields_pre_render)[#entity_type] => user [#bundle] => user [#attached] => Array([css] => Array ([0] => modules/field/theme/field.css))[summary] => Array ([blog] => Array([#type] => user_profile_item [#title] => Blog [#markup] => View recent blog entries [#attributes] => Array([class] => Array ([0] => blog)))
[#type] => user_profile_category [#attributes] => Array([class] => Array ([0] => user-member))[#weight] => 5 [#title] => History [member_for] => Array([#type] => user_profile_item [#title] => Member for [#markup] => 3 days 11 hours))[user_picture] => Array ([#markup] => [#weight] => -10))
Your module may also implement hook_user_view() to manipulate the profile items in the $user->content array before they are
themed. The following is an example of simply removing the blog profile item from the user profile page. The function is named
as if it were in the hypothetical hide.module:
/**
* Implements hook_user_view().
*/
function hide_user_view($account, $view_mode = ‘full’) { unset($account->content['summary']['blog']);}
The User Registration Process
By default, user registration on a Drupal site requires nothing more than a username and a valid e-mail address. Modules can add their own fields to the user registration form by implementing a few user hooks. Let’s write a module called legalagree.module thatprovides a quick way to make your site play well in today’s litigious society.
First, create a folder at sites/all/modules/custom/legalagree, and add the following files (see Listings 6-2 and 6-3) to the
legalagree directory.Then, enable the module via Administer -> Site building -> Modules.
Listing 6-2. legalagree.info
name = Legal Agreement
description = Displays a dubious legal agreement during user registration.
package = Pro Drupal Development core = 7.x
files[] = legalagree.module
Listing 6-3. legalagree.module
<?php
/**
* @file
* Support for dubious legal agreement during user registration.
*/
/**
* Implements hook_form_alter().
*/
function legalagree_form_alter(&$form, &$form_state, $form_id) {
// check to see if the form is the user registration or user profile form
// if not then return and don’t do anything
if (!($form_id == 'user_register_form' || $form_id == 'user_profile_form')) { return;}
// add a new validate function to the user form to handle the legal agreement
$form['#validate'][] = 'legalagree_user_form_validate';
// add a field set to wrap the legal agreement
$form['account']['legal_agreement'] = array( '#type' => 'fieldset','#title' => t('Legal agreement'));
// add the legal agreement radio buttons
$form['account']['legal_agreement']['decision'] = array( '#type' => 'radios','#description' => t('By registering at %site-name, you agree that at any time, we (or our surly, brutish henchmen) may enter your place of residence and
smash your belongings with a ball-peen hammer.', array('%site-name' => variable_get('site_name', 'drupal'))),
'#default_value' => 0,'#options' => array(t('I disagree'), t('I agree')));}
/**
* Form validation handler for the current password on the user_account_form().
*
* @see user_account_form()
*/
function legalagree_user_form_validate($form, &$form_state) {
global $user;
// Did user agree?
if ($form_state['input']['decision'] <> 1) {
form_set_error('decision', t('You must agree to the Legal Agreement before registration can be completed.'));
}
else
{
watchdog('user', t('User %user agreed to legal terms', array('%user' => $user->name)));
}
}
The user hook gets called during the creation of the registration form, during the validation of that form, and after the user
record has been inserted into the database. Our brief module will result in a registration form similar to the one shown in Figure 6-2.
Figure 6-2. A modified user registration form
Using profile.module to Collect User Information
If you plan to extend the user registration form to collect information about users, you would do well to try out
profile.module before writing your own module. It allows you to create arbitrary forms to collect data, define whether the
information is requiredand/or collected on the user registration form, and designate whether the information is public or
private. Additionally, it allows the administrator to define pages so that users can be viewed by their profile choices using a URL constructed from site URL plusprofile/ plus name of profile field plus value.
For example, if you define a textual profile field named profile_color, you could view all the users who chose black for their favorite color at http://example.com/?q=profile/profile_color/black. Or suppose you are creating a conference web site
and areresponsible for planning dinner for attendees. You could define a check box profile field named profile_vegetarian
and view all users who are vegetarians at http://example.com/?q=profile/profile_vegetarian (note that for check box
fields, the value is implicitand thus ignored; that is, there is no value appended to the URL like the value black was for theprofile_color field).
As a real-world example, the list of users at http://drupal.org who attended the 2010 Drupal conference in San Francisco,
California, can be viewed at profile/conference-sf-2010 (in this case, the name of the field is not prefixed with profile_).
The Login Process
The login process begins when a user fills out the login form (typically at http://example.com/?q=user or
displayed in a block)and clicks the “Log in” button. The validation routines of the login form check whether the username has been blocked, whether an access rule has denied access, and whether the user has entered an incorrect username or
password. The user is duly notified of any of these conditions.
Drupal attempts to log in a user locally by searching for a row in the users table with the matching username and passwordhash. A successful login results in the firing of two user hooks (load and login), which your modules can implement, as
shown in Figure 6-3.
Figure 6-3. Path of execution for a local user login
Adding Data to the $user Object at Load Time
The load operation of the user hook is fired when a $user object is successfully loaded from the database in response to a call to user_load(). This happens when a user logs in, when authorship information is being retrieved for a node, and at
several other points.
Let’s write a module named loginhistory that keeps a history of when the user logged in. We’ll display the number of times the user has logged in on the user’s “My account” page. Create a folder named loginhistory in sites/all/modules/
custom/, and add the files in Listings 6-4 through 6-6.First up is sites/all/modules/custom/loginhistory/loginhistory.info.
Listing 6-4. loginhistory.info
name = Login History
description = Keeps track of user logins.
package = Pro Drupal Development core = 7.x
files[] = loginhistory.install files[] = loginhistory.module
We need an .install file to create the database table to store the login information, so we create sites/all/modules/custom/
loginhistory/loginhistory.install.
Listing 6-5. loginhistory.install
<?php
/**
* Implements hook_schema().
*/
function loginhistory_schema() {
$schema['login_history'] = array('description' => 'Stores information about user logins.', 'fields' => array(
'uid' => array( 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' => 'The {user}.uid of the
user logging in.',),'login' => array( 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'description' =>
'Unix timestamp denoting time of login.',),),'indexes' => array( 'uid' => array('uid),),);
return $schema;}
Listing 6-6. loginhistory.module
<?php
/**
* @file
* Keeps track of user logins.
*/
/**
* Implements hook_user_login
*/
function loginhistory_user_login(&$edit, $account) {
// insert a new record each time the user logs in
$nid = db_insert('login_history')->fields(array( 'uid' => $account->uid,
'login' => $account->login))->execute();}
/**
* Implements hook_user_view_alter
*/
function loginhistory_user_view_alter(&$build){
global $user;
// count the number of logins for the user
$login_count = db_query("SELECT count(*) FROM {login_history} where uid = :uid", array(':uid' => $user->uid))->fetchField();
$build['summary'['login_history'] = array( '#type' => 'user_profile_item', '#title' => t('Number of logins'), '#markup' => $login_count, '#weight' => 10,);}
After installing this module, each successful user login will fire the login operation of the hook_user_login, which the module will respond to by inserting a record into the login_history table in the database. When the $user object is loaded duringhook_user_
view, the hook_user_view_alter function will be fired, and the module will add the current number of logins for that user to the page when the user views the “My account” page, as shown in Figure 6-4.
Figure 6-4. Login history tracking user logins
Providing User Information Categories
If you have an account on http://drupal.org, you can see the effects of providing categories of user information by logging in and clicking the “My account” link, and then selecting the Edit tab. In addition to editing your account information,
such as yourpassword, you can provide information about yourself in several other categories such as Drupal involvement,
personal information, work information, and preferences for receiving newsletters.
External Login
Sometimes, you may not want to use Drupal’s local users table. For example, maybe you already have a table of users in another database or in LDAP. Drupal makes it easy to integrate external authentication into the login process.
Let’s implement a very simple external authentication module to illustrate how external authentication works. Suppose your
company hires only people named Dave, and usernames are assigned based on first and last names.
This module authenticates anyone whose username begins with the string dave, so the users davebrown, davesmith, and
davejones will all successfully log in. Our approach will be to use form_alter() to alter the user login validation handler so that it runs our own validation handler. Here issites/all/modules/custom/authdave/authdave.info:
name = Authenticate Daves
description = External authentication for all Daves.
package = Pro Drupal Development core = 7.x
files[] = authdave.module
And here is the actual authdave.module:
<?php
/**
* Implements hook_form_alter().
* We replace the local login validation handler with our own.
*/
function authdave_form_alter(&$form, &$form_state, $form_id) {
// In this simple example we authenticate on username to see whether starts with dave if ($form_id == 'user_login' || $form_id == 'user_login_block') {$form['#validate'][] = 'authdave_user_form_validate';}}
/**
* Custom form validation function
*/
function authdave_user_form_validate($form, &$form_state) { if (!authdave_authenticate($form_state))
{
form_set_error('name', t('Unrecognized username.'));
}}
/**
* Custom user authentication function
*/
function authdave_authenticate($form_state) {
// get the first four characters of the users name
$username = $form_state['input']['name'];
$testname = drupal_substr(drupal_strtolower($username),0,4);
// check to see if the person is a dave if ($testname == "dave") {
// if it’s a dave then use the external_login_register function
// to either log the person in or create a new account if that
// person doesn’t exist as a Drupal user user_external_login_register($username, ‘authdave’); return TRUE;
} else {
return FALSE;}}
In the authdave module (see Figure 6-5), we simply swap out the second validation handler for our own.
Compare Figure 6-5 with Figure 6-3, which shows the local user login process.
Figure 6-5. Path of execution for external login with a second validation handler provided by
the authdave module (compare with Figure 6-3)
The function user_external_login_register() is a helper function that registers the user if this is the first login and then logs the
user in. The path of execution is shown in Figure 6-6 for a hypothetical user davejones logging in for the first time.
If the username begins with “dave” and this is the first time this user has logged in, a row in the users table does not exist for
this user, so one will be created. However, no e-mail address has been provided like it was for Drupal’s default local user
registration, so a module this simple is not a real solution if your site relies on sending e-mail to users.
You’ll want to set the mail column of the users table so you will have an e-mail address associated with the user.
To do this, you can have your modulerespond to the insert operation of the user hook, which is fired whenever a new user is
inserted:
/**
* Implements hook_user_insert().
*/
function authdave_user_insert(&$edit, &$account, $category = NULL) { global $authdave_authenticated;
if ($authdave_authenticated) {
$email = mycompany_email_lookup($account->name);
// Set e-mail address in the users table for this user.
db_update('users')->fields( array('mail' => $email,))->condition('uid', $account->uid)->execute();}}
Savvy readers will notice that there is no way for the code to tell whether the user is locally or externally authenticated, so we’ve
cleverly saved a global variable indicating that our module did authentication. We could also have queried the authmaptable like so
db_query("SELECT uid FROM {authmap} WHERE uid = :uid AND module = :module", array(':uid'=>$account
->uid, 'module' => 'authdave');
All users who were added via external authentication will have a row in the authmap table as well as the users table. However, in this case the authentication and hook_user_insert run during the same request, so a global variable is a good alternativeto a
database query.
Figure 6-6. Detail of the external user login/registration process
Summary
After reading this chapter, you should be able to
• Understand how users are represented internally in Drupal.
• Understand how to store information associated with a user in several ways.
• Hook into the user registration process to obtain more information from a registering user.
• Hook into the user login process to run your own code at user login time.
• Understand how external user authentication works.
• Implement your own external authentication module.
For more information on external authentication, see the openid.module (part of the Drupal core) or the contributed
pubcookie.module.
Không có nhận xét nào:
Đăng nhận xét