为了让用户可以在网页上输入注释内容,我们需要为此提供一个区域。让我们再为annotate.module添加一个表单吧:
/**
* Implementation of hook_nodeapi().
*/
function annotate_nodeapi(&$node, $op, $teaser, $page) {
global $user;
switch ($op) {
// The 'view' operation means the node is about to be displayed.
case 'view':
// Abort if the user is an anonymous user (not logged in) or
// if the node is not being displayed on a page by itself
// (for example, it could be in a node listing or search result).
if ($user->uid == 0 || !$page) {
break;
}
// Find out which node types we should annotate.
$types_to_annotate = variable_get('annotate_node_types', array('page'));
// Abort if this node is not one of the types we should annotate.
if (!in_array($node->type, $types_to_annotate)) {
break;
}
// Add our form as a content item.
$node->content['annotation_form'] = array(
'#value' => drupal_get_form('annotate_entry_form', $node),
'#weight' => 10
);
break;
}
}
这些代码看其来十分复杂,所以让我们一起搞定它吧。首先要注意的是我们实现了另外一个Drupal的钩子——nodeapi钩子,它会在Drupal对节点做各种行为的时候被调用,这样其他模块(比如说我们的)就可以在程序进行下去之前对节点进行改动。变量$node给出了一个节点。而它前面的&符号表明这是对它本身的一次真实提取,这意味着我们在模块中对$node进行的任何改动都将被保存下来。因为我们打算呈现一个表单,所以能够修改节点是一件很值得高兴的事。
Drupal同样提供了一些信息来说明当我们的模块被调用的时候Drupal本身在进行什么活动。这些信息体现在变量$op(operation)的取值上,它们可以是insert(节点正在被创建),delete(节点正在被删除),或者其他一个或多个值。而现在的情况是,我们只对即将被显示的节点有兴趣,那么$op的值也就应该是view。代码结构上,我们使用switch语句,这样可以方便的指出我们的模块在各种情况下应该做些什么。
然后,我们迅速的检查一下在哪些情况下不应该把注释显示出来。其中一种情况就是当访问节点的用户没有登录(要注意的是,我们使用的关键字global将变量$user引入进来,这样就可以检验当前用户是否已登录了),另外一种应该避免显示注释的情况是变量$page的值不为TRUE的时候。如果$page的值是FALSE,那么节点本身就并没有显示出来,而是显示在某个列表里,比如搜索引擎的结果或者最新节点的列表。在这中情况下我们可不想添加任何东西。所以使用break语句来结束switch语句以避免对页面做出任何修改。
在把注释表单添加到网页中之前,我们需要确认当前编写的页面的类型是否在设置页面中启用了注释功能,所以我们将实现settings功能时保存的数组再检索出来——我们曾用一个很具有描述性的名字记录了它,$types_to_annotate。再次使用variable_get()函数,我们仍然为它定义了默认值,以应对网站管理员未曾在annotate的设定页面做出选择的情况。而下一步就是检验我们正在处理的节点的类型是不是被包含在$types_to_annotate之中了。然后,如果此节点不是我们想添加注释的种类,则再次使用break语句。
最后的尝试便是创建表单并把它添加到节点中去。首先是在annotate.module中建立一个独立的函数,其唯一目的就是定义表单,以便稍后用于添加到节点中去:
/**
* Define the form for entering an annotation.
*/
function annotate_entry_form($form_state, $node) {
// Define a fieldset.
$form['annotate'] = array(
'#type' => 'fieldset',
'#title' => t('Annotations'),
);
// Define a textarea inside the fieldset.
$form['annotate']['note'] = array(
'#type' => 'textarea',
'#title' => t('Notes'),
'#default_value' => isset($node->annotation) ? $node->annotation : '',
'#description' => t('Make your personal annotations about this content here.Only you (and the site administrator) will be able to see them.')
);
// For convenience, save
$form['annotate']['nid']
'#type' => 'value',
'#value' => $node->nid,
);
// Define a submit functi
$form['annotate']['submit
'#type' => 'submit',
'#value' => t('Update')
);
return $form;
}
函数有两个参数。第一个$form_state,对于它Drupal的所有表单函数都是自动传递的,我们先不管它,详细的情况将在第10章formAPI进行讨论。第二个参数是$node,是我们用来传给早先实现的nodeapi钩子中的drupal_get_form()函数的。
建立表单的过程和我们之前在annotate_admin_settings()函数中做的基本一致,都是通过建立一个带有键值的数组——只不过这次我们要将文本框和提交按钮放在同一个区域,这样在网页上他们就可以显示在同一组内容中了。首先,建立一个数组,将它的#type键的值设定为‘fieldset’,并给它加个标题。然后建立一个数组用来描述文本区。注意一点,textarea数组的键是fieldset数组的一员。换句话说,我们将使用$form['annotate']['note']而不是$form['note']。这样的话Drupal就能判断出textarea的元素是fieldset元素的一员。我们使用了一个三元操作符来判断注释的默认值是要显示一个已存在的注释,或者是当前没有注释的话就显示一个空的字符串。最后,我们建立了提交按钮,并返回数组,完成了对我们这个表单的定义。
将注意力回到annotate_nodeapi()函数上,我们通过对节点内容赋予“值”和“权重”来实现将表单显示在网页内容中。这里“值”主要指明应该显示什么,而“权重”则是告知Drupal应把你的内容和这节点中的其他内容按照一个什么样顺序关系显示出来。我们要的是一个在页面下部的注释表单,所以我们给了它一个比较大的权重,10。而我们想要显示的是表单,所以就要调用drupal_get_form()来使我们的表单从一个描述它内容的数组变成一个完整的HTML表单。注意一下我们是如何将$node传给表单函数的,通过它能获得以前的已经存在的注释,并把它预先填在表单中 。
建立一个节点,并在你的浏览器中访问它,你应该看到注释表单了吧。(见图2-2)
图2-2 注释表单显示在网页上的情形
当我们点击提交按钮,会发生什么事情呢?什么也不会发生,因为我们还没有写任何代码去对这个表单进行操作。我们现在就去加上吧。不过在这之前,我们必须考虑一下用户输入的内容将要被存储在哪里。
在数据库表中存储数据
最常用的方法是建一个独立的数据库表来为模块存储数据,这样可以将数据和Drupal的核心表区分开来。当决定模块使用什么字段的时候,你应该问问自己:我都需要存储什么数据?如果我对这个表进行查询,我需要哪些字段和索引呢。最后是,我对自己的模块有什么更长远的计划么?
我们需要保存的数据只是一些来自annotation的简单文本:节点的ID,撰写注释的用户的ID。记录时间戳也可能有用,那样我们就可以建立一个按时间顺序排列的列表,来显示最近更新的注释。最终我们要问的问题是:“注释对于这个用户和这个节点有什么用?”我们将基于uid(用户ID)和nid(节点ID)建立一个混合索引,这样可以使查询变得尽可能的快。我们的表所用到的SQL语句应该像下面这样:
CREATE TABLE annotate (
uid int(10) NOT NULL,
nid int(10) NOT NULL,
note longtext NOT NULL,
when int(11) NOT NULL default '0',
PRIMARY KEY (uid, nid),
);
我们可以只是把以上的SQL语句放在模块附带的README.txt文件中,让其他安装我们模块的人自己在数据库中添加相应的数据表。不过,我们可以利用Drupal的一些方便功能,使数据表在模块启用的同时就被创建。我们将创建这样一个特殊的文件:文件名和模块名保持一致,却以.install为后缀,例如如果模块是annotate.module那么这个文件就将是annotate.install。
好的,让我们创建文件sites/all/modules/custom/annotate/annotate.install,然后输入以下内容:
<?php
// $Id$
/**
* Implementation of hook_install().
*/
function annotate_install() {
// Use schema API to create database table.
drupal_install_schema('annotate');
}
/**
* Implementation of hook_uninstall().
*/
function annotate_uninstall() {
// Use schema API to delete database table.
drupal_uninstall_schema('annotate');
// Delete our module's variable from the variables table.
variable_delete('annotate_node_types');
}
/**
* Implementation of hook_schema().
*/
function annotate_schema() {
$schema['annotations'] = array(
'description' => t('Stores node annotations that users write.'),
'fields' => array(
'nid' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => t('The {node}.nid to which the annotation applies.'),
),
'uid' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => t('The {user}.uid of the user who created the annotation.')
),
'note' => array(
'description' => t('The text of the annotation.'),
'type' => 'text',
'not null' => TRUE,
'size' => 'big'
),
'created' => array(
'description' => t('A Unix timestamp indicating when the annotation
was created.'),
'type' => 'int',
'not null' => TRUE,
'default' => 0
),
),
'primary key' => array(
'nid', 'uid'
),
);
return $schema;
}
在这里,我们通过实现schema钩子来描述了一个模式,而当annotate模块第一次被启用的时候,Drupal会搜寻这个叫annotate.install的文件,并运行annotate.install()函数,从而解读这个模式。我们描述了我们希望Drupal创建的数据表和字段,而Drupal将他们翻译成我们所使用的数据库的语句(如果想知道这一切工作是如何进行的,请看第5章)。如果一切顺利,数据表就已经建好了。但是因为我们刚才已经启用的这个模块,所以现在要在有.install文件的情况下将它重新安装一次。
请按如下步骤操作:
1 在Administer > Site building >Modules page页面关闭这个模块。
2 在Administer > Site building >Modules page页面的卸载表中卸载这个模块。这是正确操作模块的方法,它将使Drupal当模块从未存在过。
3 启用模块。而这次,数据表会在同一时间被创建。
Tip:如果你因为在install文件中范了语法错误或者其他的失误而导致了失败,按照类似上面的方法卸载模块和数据表可以使一切还原。而到了万不得已的时候,删除数据库中system表中关于此模块的那行也可以达到同样的目的。
在Drupal创建了用来储存数据的anntantions表之后,我们就得对代码做些修改了。不为别的,只因为一旦用户输入了注释的内容并点击提交按钮,我们必须用程序来对这些行为做出处理。
提交函数如下:
/**
* Handle submission of the annotation form and saving
* of the data to the database.
*/
function annotate_entry_form_submit($form, $form_state) {
global $user;
$note = $form_state['values']['note'];
$nid = $form_state['values']['nid'];
db_query('DELETE FROM {annotations} WHERE nid = %d AND uid = %d',
$nid, $user->uid);
db_query("INSERT INTO {annotations} (nid, uid, note, created) VALUES
(%d, %d, '%s', %d)", $nid, $user->uid, $note, time());
drupal_set_message(t('Your annotation has been saved.'));
}
因为我们只允许每个用户对每个节点做一条注释,所以我们可以安全的删除旧的注释(如果有的话),并将新的插入到数据库中。不过在操作数据库的时候,有几件事情需要注意一下。第一,我们不需要担心和数据库的连接问题,因为Drupal在启动阶段就将它完成了。第二,只要涉及到数据表,我们就将它放在大括号中,这样是为了便于自动对数据表的前缀进行操作(有关数据表前缀化的更多信息,参见sites/default/settings.php)。第三,我们在查询语句中使用了文本占位符(%d是整型变量的文本占位符,而%s是字符型的)以供变量进行替换,好让Drupal内置的安全机制就可以起到防止SQL注入攻击的作用。然后,drupal_set_message()函数在用户会话中放置了一条信息,并在用户访问下一个页面的时候作为反馈信息显示出来。
最后,在将表单指派到$node->content中去之前,为了将可能已经存在的注释从数据库中提取出来填到注释表单中去,我们要修改一下nodeapi钩子的代码。下面代码中黑体的部分就是要添加的:
/**
* Implementation of hook_nodeapi().
*/
function annotate_nodeapi(&$node, $op, $teaser, $page) {
global $user;
switch ($op) {
// The 'view' operation means the node is about to be displayed.
case 'view':
// Abort if the user is an anonymous user (not logged in) or
// if only the node summary (teaser) is being displayed.
if ($user->uid == 0 || !$page) {
break;
}
// Find out which node types we should annotate.
$types_to_annotate = variable_get('annotate_node_types', array('page'));
// Abort if this node is not one of the types we should annotate.
if (!in_array($node->type, $types_to_annotate)) {
break;
}
// Get the current annotation for this node from the database
// and store it in the node object.
$result = db_query('SELECT note FROM {annotations} WHERE nid = %d
AND uid = %d', $node->nid, $user->uid);
$node->annotation = db_result($result);
// Add our form as a content item.
$node->content['annotation_form'] = array(
'#value' => drupal_get_form('annotate_entry_form', $node),
'#weight' => 10
);
break;
case 'delete':
db_query('DELETE FROM {annotations} WHERE nid = %d', $node->nid);
break;
}
}
我们第一步做的就是在数据库中搜寻属于当前用户和节点的注释。然后,使用了db_result()函数,这个函数的作用是得到上面的查询结果的第一条数据的第一个字段,在这个例子里由于我们只允许每人对每字节发表一个注释,所以数据应该始终只有一条。
之后是又给nodeapi钩子加了个删除的功能,使得注释将会随着节点的删除而删除。
调试一下你的模块吧,它应该可以存取注释了。该是小小的高兴一下的时候了——你已经从零开始完成了一个Drupal的模块,现在,你算是踏上了成为Drupal高手的路了。
Taxonomy upgrade extras