hook_user()入门(Introduction to hook_user())
译者:老葛 Eskalate科技公司
在你的模块中实现钩子hook_user()使你能够对用户帐号进行不同的操作,以及修改$user对象。着我们看一下这个函数的签名:
function hook_user($op, &$edit, &$user, $category = NULL)
参数$op用来描述对用户帐号所进行的当前操作,它可以有多个不同的值:
• after_update:在$user被保存到数据库中以后调用。
• categories:返回一个关于分类的数组,当用户编辑用户帐号时,这些分类被当作Drupal菜单本地任务(menu local tasks)。参看profile.module中profile_user()的一个实现。
• delete: 刚刚从数据库中删除了一个用户。这给了模块一个机会,用来从数据库中删除与该用户相关的信息。
• form:在被展示的用户编辑表单中插入一个额外的表单字段元素。
• insert:一个新的用户帐号将要被创建并保存到数据库中。
• login:用户已经成功登录。
• logout:用户刚刚退出登录,他/她的会话已被销毁。
• load:已成功加载用户帐号。模块可以向$user对象添加额外的信息。
• register:用户帐号注册表单将被展示。模块可以向表单中添加额外的表单元素。
• submit: 用户编辑表单已被提交。在帐号信息发送给user_save()以前可对其进行修改。
• update:已存在的用户帐号(修改后)将要被保存到数据库中。
• validate: 用户帐号已被修改。模块应该验证它的定制数据并生成任何必要的错误消息。
• view: 正在展示用户的帐号信息。模块应该把它要添加到展示页面中的定制信息放到一个数组中并将数组返回。查看操作最后调用theme_user_profile来生成用户个人资料页面。马上就可以看到更详细的解释了。
参数 $edit是一个数组,当用户帐号正被创建或者更新时提交的表单数值组成了这一数组。注意它是通过引用传递的,所以你对它所做的任何修改都将实际的改变表单数值。
$user对象也是通过引用传递的,所以你对它所做的任何修改都将实际的改变$user信息。
参数$category是正被编辑的当前用户帐号的分类。
警告 不要混淆了hook_user()中的参数$user和全局变量$user对象。参数$user是当前正被操作的帐号的用户对象。而全局变量$user对象是当前登录的用户。
理解hook_user('view')(Understanding hook_user('view'))
模块可以使用hook_user('view')来向用户个人资料页面添加信息(例如,你在http://example.com/?q=user/1看到的;参看图6-1)
图6-1 用户个人资料页面,这里日志模块和用户模块使用hook_user('view')添加了额外的信息
让我们看一下日志模块是如何向这一页面添加它的信息的:
function blog_user($op, &$edit, &$user) {
if ($op == 'view') {
$items['blog'] = array(
'title' => t('Blog'),
'value' => l(t('View recent blog entries'), "blog/$user->uid"),
'class' => 'blog', // CSS selector class to add.
);
return array(t('History') => $items);
}
}
查看操作返回了一个关联数组的关联数组。外面的数组使用分类名称作为键。在前面的例子中它是History(历史)。内部的数组应该有一个唯一的文本键(在这里为blog)并带有title, value, 和 class元素。比较该代码片段和图6-1,你将看到这些元素是如何被显示的。
在调用user.module中的theme_user_profile()以前,你的模块也可以通过实现hook_profile_alter()来操作个人资料项目,。下面这个的例子,简单的从你的用户个人资料页面删除了日志项目:
/**
* Implementation of hook_profile_alter().
*/
function hide_profile_alter(&$account, &$fields) {
unset($fields['History']['blog']);
}
用户注册流程(The User Registration Process)
默认情况下,Drupal站点的用户注册只需要一个用户名和一个有效的e-mail地址就可以了。模块可以通过实现用户钩子来向用户注册表单添加它们自己的字段。让我们编写一个名为legalagree.module的模块,它提供了一个快速的方式来使你的站点适应今天这个好诉讼的社会。
首先在sites/all/modules/custom下面创建一个名为legalagree的文件夹,并向legalagree目录添加下面的文件(参看列表6-1和6-2)。接着通过Administer ➤ Site building ➤ Modules来启用模块。
列表 6-1. legalagree.info
; $Id$
name = Legal Agree
description = Displays a dubious legal agreement during user registration.
version = "$Name$"
列表 6-2. legalagree.module
<?php
// $id$
/**
* @file
* Support for dubious legal agreement during user registration.
*/
/**
* Implementation of hook_user().
*/
function legalagree_user($op, &$edit, &$user, $category = NULL) {
switch($op) {
// User is registering.
case 'register':
// Add a fieldset containing radio buttons to the
// user registration form.
$fields['legal_agreement'] = array(
'#type' => 'fieldset',
'#title' => t('Legal Agreement')
);
$fields['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'))
);
return $fields;
// Field values for registration are being checked.
// (Also called when user edits his/her 'my account' page, but
// $edit['decision'] is not set in that case.)
case 'validate':
// Make sure the user selected radio button 1 ('I agree').
// the validate op is reused when a user updates information on
// The 'my account' page, so we use isset() to test whether we are
// on the registration page where the decision field is present.
if (isset($edit['decision']) && $edit['decision'] != '1') {
form_set_error('decision', t('You must agree to the Legal Agreement
before registration can be completed.'));
}
return;
// New user has just been inserted into the database.
case 'insert':
// Record information for future lawsuit.
watchdog('user', t('User %user agreed to legal terms',
array('%user' => $user->name)));
return;
}
}
在创建注册表单期间,在表单验证期间,还有在用户记录被插入到数据库中以后,都要调用用户钩子。我们这个简洁的模块所导致的注册表单类似于图6-2所示的。
图6-2 一个修改了的用户注册表单
使用profile.module来收集用户信息
如果你想扩展用户注册表单来收集用户信息,在你打算编写自己的模块以前,你可以先试用一下 profile.module。它允许你创建任意的表单来收集数据,在用户注册表单上定义信息是否是必须的(或者收集的),指定信息是公开的还是私有的。例外,它允许管理员定义页面,这样就可以根据用户的个人资料选项,使用一个由“ 站点URL” + “ profile/” + “个人资料字段的名字” +“值“ (参看图6-3)构建的URL,来查看用户了。
图6-3 用来创建额外用户个人资料字段的页面
例如,如果你定义了一个名为profile_color的文本字段,你可以使用http://example.com/?q=profile/profile_color/black来查看所有的选择了黑色作为他们喜欢的颜色的用户。或者假定你正在创建一个会议网站,并负责为参加者计划宴会。你可以定义一个名为profile_vegetarian的复选框作为个人资料字段,并可在http://example.com/?q=profile/profile_vegetarian(注意,对于复选框,已隐含了值,因此这里忽略了它)查看所有的素食用户。
在Drupal官方网站http://drupal.org上可以找到一个实际中的例子,2006年参加加拿大范库弗峰地区Drupal会议的用户列表,可以在地址http://drupal.org/profile/conference-vancouver-2006中看到(这里,字段名前面没有加前缀“profile_ “)。
提示 只有在个人资料字段设置中填充了字段Page的标题时,自动根据个人资料创建总结页面才正常工作,但它不适用于textarea,URL,或者日期字段。
登录流程(The Login Process)
当用户填完登录表单(一般位于http://example.com/?q=user 或者在一个区块中)并点击登录按钮时,登录流程开始。
登录表单的验证程序检查用户名是否被封锁了,无论是根据访问规则拒绝访问,还是由于用户输入了一个错误的密码。如果任何一种情况发生了,都会及时的通知用户。
在Drupal中可以使用本地和外部认证两种方式。外部认证系统的;例子包括LDAP, Pubcookie, Sxip,以及其它。一种外部认证类型是分布式认证,在这里来自于一个Drupal站点的用户可以登录到另一个Drupal站点(参看Drupal内核中的drupal.module)。
Drupal将首先尝试从本地登录,在users表中查找是否存在匹配用户名和密码哈希值的记录。如果不成功,Drupal将尝试外部认证(参看图6-4).成功的本地登录将导致调用两个用户钩子(load 和 login),在你的模块中可以实现这些钩子。
图6-4 用户登录的执行路径
向$user对象添加数据
通过调用user_load(),从数据库中成功的加载一个$user对象时,将调用用户钩子的加载(load)操作。当一个用户登录(或退出)时,当从一个节点取回作者信息时,或者一些其它情况时,都会调用这一加载(load)操作。
注意 为了性能优化,当为一个请求实例化当前$user对象时(参看前面的“$user对象“部分),没有调用user_load()。如果你正在编写你自己的模块,在调用一个需要用到完整加载了的$user对象的函数以前, 总要调用user_load(),除非你能保证已经完整加载了$user对象。
让我们创建一个名为“loginhistory”的模块,用来保存用户登陆的历史记录。在sites/all/modules/custom/下面创建一个名为loginhistory的文件夹,并添加以下文件(参看列表6-3到6-5)。首先是loginhistory.module。
列表 6-3. loginhistory.module
<?php
// $Id$
/**
* @file
* Keeps track of user logins.
*/
/**
* Implementation of hook_user().
*/
function loginhistory_user($op, &$edit, &$account, $category = NULL) {
switch($op) {
// Successful login.
case 'login':
// Record timestamp in database.
db_query("INSERT INTO {login_history} (uid, timestamp) VALUES (%d, %d)",
$account->uid, $account->login);
break;
// $user object has been created and is given to us as $account parameter.
case 'load':
// Add the number of times user has logged in.
$account->loginhistory_count = db_result(db_query("SELECT COUNT(timestamp) AS
count FROM {login_history} WHERE uid = %d", $account->uid));
break;
// 'My account' page is being created.
case 'view':
// Add a field displaying number of logins.
$items['login_history'] = array(
'title' => t('Number of Logins'),
'value' => $account->loginhistory_count,
'class' => 'member'
);
return array(t('History') => $items);
}
}
我们需要一个.install文件来创建数据库表来存储登录信息,所说义我们创建loginhistory.install。
列表 6-4. loginhistory.install
<?php
// $Id$
/**
* Implementation of hook_install().
*/
function loginhistory_install() {
switch ($GLOBALS['db_type']) {
case 'mysql':
case 'mysqli':
db_query("CREATE TABLE {login_history} (
uid int NOT NULL default '0',
timestamp int NOT NULL default '0',
KEY (uid)
) /*!40100 DEFAULT CHARACTER SET UTF8 */");
break;
case 'pgsql':
db_query("CREATE TABLE {login_history} (
uid int_unsigned default '0',
timestamp int_unsigned NOT NULL default '0',
KEY (uid)
)");
break;
}
}
/**
* Implementation of hook_uninstall().
*/
function loginhistory_uninstall() {
db_query("DROP TABLE {login_history}");
}
下面是loginhistory.info文件
列表6-5. loginhistory.info
; $Id$
name = Login History
description = Keeps track of user logins.
version = "$Name$"
在安装这个模块以后,每次成功的用户登录都将调用用户登录钩子,在钩子中模块将向数据库表login_history中插入一条记录。当加载$user对象时,将调用用户加载钩子,此时模块将把用户的当前的登录次数添加$user->loginhistory_count。当用户查看“my account”(“我的帐号“)页面时,登录次数将被展示出来如图6-5所示。
图6-5 追踪用户的登录历史
注意 当你在你的模块中为对象$user或$node添加变量时,在变量名前面最好加上前缀,以避免命名空间的冲突。这就是为什么这里使用loginhistory_count来代替count的原因。
尽管在“my account”页面,我们展示了我们添加到$user上的额外的信息,记住由于$user对象是全局变量,其它模块也能访问它。我们留给读者一个非常有用的联系,为了安全性(“喂!我今天上午3:00没有登录“),修改前面的模块,在左(或右)栏的区块中来提供一个美观的历史登陆列表。