Drupal模块开发指南


From: http://drupal.org/developing/modules
Related:

 

Drupal模块开发介绍

Drupal模块就是由PHP写成的包含了子过程的文件集. 因为这些模块在整个站点的上下文中运行, 因此它能使用基于主引擎的所有函数,访问所有的变量和结构. 事实上,一个模块和普通的PHP文件没任何不同: 它更多的是一种基于良好的设计结构和开发模型的概念. 这种类积木性质的方式很适合开源的开发模式, 因此它能允许一群开发者不必冒互相干扰的风险.

这种方法会在引擎的特殊部分执行, 然后这些能够增强现有功能的代码就会被运行. 那些能执行代码的特殊部位,通过定义一定的接口得到实现的被称之为“钩子(hooks)”.

钩子(hook)生效的时候就是引擎调用模块的导出函数的时候. 这可以通过遍历模块目录实现. 假定你的模块的名字是foo (例如modules/foo.module), 并且导出函数被称之为foo_bar(). 如果Drupal安装了一个名叫bar的钩子(hook), 那么引擎就会调用你的foo_bar()函数.

创建drupal 6.x模块

本章讲述你如何在Drupal 6.x中创建一个模块. 包含了创建.install和.info文件, 以及和Drupal菜单进行交互等.

创建模块 — 基于drupal 6.x的教程

本教程描述了如何创建基于drupal 6的模块.

模块是衔接到drupal的函数的集合, 提供了比drupal基本安装更多的功能. 读完指南后, 你将能够开发出一个基本的模块, 并且能用此模块作为模块开发更高级的模块和节点(node)模块.

本教程并不是能使你制作出可发行的模块. 它既不涉及缓存, 也不会详细的讨论权限和安全问题. 本教程仅仅是一个起点, 通过参考别的模块和学习drupal手册, 还有如何写安全的代码和代码标准文档获取更多信息.

本教程假定你拥有:

  • 基础的PHP知识, 包括语法和一些PHP对象概念
  • 基本能够理解数据库表, 列, 记录和SQL语句
  • 已经安装好的Drupal程序
  • Drupal的管理员权限
  • web服务器权限

 

本教程假定你对Drupal模块的内部流程一无所知. 本教程对于你写基于Drupal 5或以前的模块没有任何帮助.

入门

本教程将以一个能列出最近的blog文章或论坛话题的连接列表的模块为例子. 本文将描述如何创建初始化模块文件和目录.

第一步是为你的模块起一个简短的名字. 该名字将在所有的文件和函数名中被使用, 所以必须以一个字母开始, 另外按照drupal的规定, 它只能包含小写字母和下划线. 例如, 我们选择“onthisdate“作为这个名字.
注意: 此名字将在实现hook时作为模块文件的名字和方法前缀的名字. drupal将只能识别拥有该名字为前缀和钩子(hook)同名后缀的函数名.

在给定了名字“onthisdate“之后, 开始在drupal的安装路径创建一个目录: sites/all/modules/onthisdate. 你可能需要首先创建一个sites/all/modules目录. 在目录sites/all/modules/onthisdate里面, 建一个PHP文件并把它保存为onthisdate.module. 在drupal 6中, sites/all/modules是适合放非核心模块的地方(非核心主题包放在sites/all/themes里), 因为这里是放置涉及所有站点文件的地方. 这就使得你能简单的升级核心文件而不需要变更你自己做出的修改. 另外,假如你由多个站点的drupal安装, 并且, 假如你需要此模块只能被一个站点使用,那么就放到sites/your-site-folder/modules里.

所有你模块中被drupal使用的函数都将被命名为{模块名}_{钩子}的形式, 钩子(hook)是预先定义好的函数后缀. drupal将会通过这些函数来获取特别的数据, 所以通过这些已经定义好的名字, drupal就能知道怎么找到. 等会等我们就会讲到钩子(hook).

让drupal识别你的模块

所有的模块都必须拥有一个{模块名}.info的文件, 该文件包含了一些有关该模块的信息.

通常的格式如下:
 

; $Id$
name = Module name
description = A description of what your module does.
core = 6.x

对于本例, 该文件将被命名为“onthisdate.info”. 没有该文件, 那么在模块列表中就不会出现该模块. 本例中, 文件内容为:
; $Id$
name = On this date
description = A block module that lists links to content such as blog entries or forum discussions that were created one week ago.
core = 6.x

Info文件细节
  • name (必须的)
    作为模块的显示名称. 应该根据drupal 6的大小写标准: 词的首字母大写(“Example module”, 而不是“example module“或者“Example Module”).
  • description (必须的)
    简短的单行的描述, 在模块管理页面告诉管理员该模块的用处. 注意, 过程的描述将使页面工作不正常,所以尽量简短. 此处将限制为至多255字符.
    注意特殊字符需要使用HTML实体值, 如果仅仅是单引号, 那么只需要在外围使用双引号括起来就行了.
  • core (必须的)
    在版本6.x中, 假如没有显示的声明正确的版本信息, drupal核心将会拒绝启用或运行该模块. .info文件必须指明那些任意导入的模块或主题包拥有和drupal核心一致的兼容性. 这就是把核心信息放在.info中的含义.
  • dependencies (可选的)
    其它的一些可选项也有可能在.info文件中出现, 其中一个就是模块的依赖关系. 如果一个模块需要其它一些模块被启用, 那么就通过类似数组的形式列出来: dependencies[] = {模块名称}
  • package (可选的)
    如果一个模块定义了包的名字, 那么在admin/build/modules页里, 它将和同类包名的模块显示在一个列表里. 如果未指定包名, 它将会简单的被列在‘Other’里.
    这些是建议的包名: Audio, Bot, CCK, Chat, E-Commerce, Event, Feed Parser, Organic groups, Station, Video, Views, Voting

 

Help 钩子
通过实现drupal的hook_help(), 我们能够提供更多关于我们模块的帮助和更多信息. 因为使用了.info文件, 现在此钩子变为可选的了. 尽管如此,实现它是个很好的想法. 为了实现这个钩子, 我们只需要用你的模块名代替“hook“在你的模块文件里产生这样的一个函数就行了. 因此在我们的例子模块中, 实现hook_help(), 只需要在onthisdate.module文件里实现一个onthisdate_help()方法就ok了:
 

<?php
function onthisdate_help($path, $arg) {
 
}
?>

$path参数提供了帮助功能的上下文: 用户访问帮助所处的drupal或模块的位置. 建议通过switch语句来处理该值. 这种方式在drupal代码中很常见. 这里是一个简单的实现:
<?php

function onthisdate_help($path, $arg) {
  $output = '';  // 声明输出变量
  switch ($path) {
    case "admin/help#onthisdate":
      $output = '<p>'.  t("Displays links to nodes created on this date") .'</p>';
      break;
  }
  return $output;
} // function onthisdate_help
?>

 

告诉drupal谁可以访问你的模块

下个我们要写的函数是权限的函数, 在drupal实现中是hook_perm(). 这里是你定义权限名的地方 — 它不进行授权或阐述此权限的含义, 它只是指出针对此模块需要有哪些权限. 在先前的章节中我们实现了help钩子, 这里我们通过在onthisdate.module文件里建立一个onthisdate_perm()函数来实现hook_perm(). 该函数将仅仅返回一些将被用到的权限名称的列表; 如果你在模块的hook_perm()实现中定义了这些权限, 那么管理员将通过Administer » User management » Permissions页的设定来决定哪些角色可以访问你的模块. 这里是我们实现的例子:
 

<?php

function onthisdate_perm() {
  return array('access onthisdate content');
} // function onthisdate_perm()
?>

如果你希望有更加精准的权限控制, 那么你可以扩展这个权限集, 例如:
<?php
return array('access onthisdate content', 'administer onthisdate');
?>

针对本教程, 我们只使用一个权限. 以后我们会增加其它权限.

 

你的权限值是任意选择的, 但是必须在所有已安装的模块中是唯一的. 否则, 一个事件名将会拥有两种权限. 因此, 权限值应该包含了你的模块名, 因为这样可以避免和其它模块的名称冲突. 建议的针对权限的名称约定是”{动词} {模块名}”. 以下面这个例子作为你开发其它模块的例子:
 

<?php
function newmodule_perm() {
  return array('access newmodule', 'create newmodule', 'administer newmodule');
} // function newmodule_perm
?>

 

声明块内容

模块被用来做各种各样的事情: 一些模块产生区块(通常出现在页面左边或右边的简要内容), 其它的比如产生特别的内容类型(针对整个页面), 其它例如跟踪后台信息, 还有做以上所有的事情. 你可能听说过短语“块模块”, 被用来产生块内容(例如菜单模块)或者“节点模块“被用来描述产生整页内容的块(例如blog和forum模块). 在这个阶段, 此模块是个“块模块”, 因为它生成一个块.

本节将定义一个块显示最近的blog和forum的帖子. 产生块的钩子被称为“hook_block”. 在onthisdate.module文件中实现该钩子的函数称之为onthisdate_block().

这里是基本的格式:
 

<?php

function onthisdate_block($op = 'list', $delta = 0, $edit = array()) {
 
  // YOUR MODULE CODE HERE
 
} // function onthisdate_block
?>

块函数有三个参数:
  • $op (或者 operation)
    hook_block()函数在执行4个不同的操作时被调用, 这些操作是“list”, “view”, “save“和“configure”. 该参数告诉你的函数, 现在哪个操作被执行了. 我们将在后面讨论“list“操作, “view“操作会在下一节中描述. “configure“和“save“操作将允许你的块有一个可配置的表单和保存这些配置 — 在本例中, 我们不需要使用他们.
  • $delta
    你的模块能够定义更多的块, 在“list“操作中. 因此每个块都有一个“delta“号码, 通常是个数字, 并且在其它的一些操作中, drupal会把操作传递给“delta“号码所指出的某个块中. 本例只有一个块. 核心的drupal用户模块是一个采用多块模块的例子: 用户登录块, 谁是新人块, 谁在线块.
  • $edit
    只在“save“操作中被使用, 在本文中不讨论.

 

第一个操作是“list“操作, 当模块被应用的时候, 就会列出来. 这里定义了它们如何在Administer >> Site Building >> Blocks中出现(当在管理员页创建块列表的时候, 块模块将会调用此函数并传递一个$op=‘list’参数).

这里是onthisdate_block()函数的实现细节(下一节将更多):
 

<?php

function onthisdate_block($op = 'list', $delta = 0, $edit = array()) {
if ($op == "list") {
    // 在admin/block页里产生块列表
    $block = array();
    $block[0]["info"] = t('On This Date');
    return $block;
  }
} // function onthisdate_block
?>

好, 现在我们来讨论这些代码…

 

$block – $block就是在返回它之前存储一些必须数据的变量
$block[0] – $block是一个数组类型变量, 每个元素都表示了你所提供的一个独立的块(我们的例子里只提供了一个块). “0“的数组索引是$delta值, 该值会在后面的操作中用到(如果我们定义多个块, 我们就需要$block[0], $block[1], $block[2]等; 我们也需要检查该值为了后面的操作).
$block[0][“info”] – 关键字有可能是“info”, “cache”, “weight”, “status”. 那个可能需要额外花一节课事件进行介绍, 所以我们只介绍“info”, 该值的功能是在管理员菜单中给我们的块起个人性化的名字. 因此, 在hook_block()中, 我们知道它的delta值是0, 但是管理员知道它叫“On This Date”.

下一节将产生块内容, 通过“view“操作实现.

产生块内容

下一步就是产生块的内容了. 这将涉及访问drupal数据库的概念. 我们的目标是获取一个一周内的内容列表(作为节点存在放数据库中). 具体地说, 我们需要午夜到一周前的11:59pm内容. 当一个节点初次创建, 产生的时间就将被存放到数据里.

为了告诉drupal我们所放在block中的内容, 我们使用hook_block()的‘view’操作符. 因此, 我们需要在我们先前定义好的onthisdate_block()中增加更多的代码.

首先, 我们需要计算一周前午夜的时间和一周前11:59pm时的时间. 这部分代码是独立的.
 

<?php

function onthisdate_block($op='list', $delta=0) {
 
  if ($op == "list") {
    // Generate listing of blocks from this module, for the admin/block page
    $block = array();
    $block[0]["info"] = t('On This Date');
    return $block;
  }
  else if ($op == 'view') {
 
    // Generate our block content
 
    // Get today's date
    $today = getdate();
 
    // calculate midnight one week ago
    $start_time = mktime(0, 0, 0,
       $today['mon'], ($today['mday'] - 7), $today['year']);
 
    $end_time = time();
 
   // more coming...
  }
}
?>

下一步就是从数据库中获取我们需要内容的SQL语句了. 我们将检索node表的内容, 此表是drupal的核心表. 我们能通过这个查询获取所有类型的内容: blog内容, 论坛帖子等. 对于本文, 这已经足够了. 对于一个真实的模块, 你可能需要调整SQL语句来指定需要获取的特殊内容(通过增加type列和WHERE子句来检索类型信息).

 

drupal使用数据库帮助函数来执行数据库查询. 那个意思是说, 大部分情况, 你可以写你的SQL语句而不需要关心数据库的连接情况. 因此我们使用db_query()来得到记录(例如: 数据库行):
 

<?php
  $query = "SELECT nid, title, created FROM " .
     "{node} WHERE created >= '%d' " .
     " AND created <= '%d'";
 
  $query_result =  db_query($query, $start_time, $end_time);
?>

上面描述了如何在drupal中进行 安全查询: 通过使用“占位符“例如%d和%s来创建查询, 然后把变量传递给db_query()来填充那些“占位符”. 这种方式能够阻止SQL注入攻击, 特别是在查询中使用用户产生的内容的时候. 另外一个值得注意的地方是这里进行查询的drupal数据库表名被大括号包围, 为{node}. 这是必须的,这样你的模块就能支持表名前缀. 最后, 我们还应该注意到我们并不关心访问nodes的权限. 通常情况下所有的基于nodes的查询都需要使用db_rewrite_sql()函数, 该函数确保浏览某页的用户有权看到此node的返回信息, 但是这些超出了本文的内容.

 

现在, 我们使用db_fetch_object()来查看通过上面的查询返回的单独的记录. 对于我们所找到的单个节点, 我们生成一个包含节点标题指向该节点的连接:
 

<?php
  // content variable that will be returned for display  
  $block_content = '';
  while ($links = db_fetch_object($query_result)) {
    $block_content .=  l($links->title, 'node/'. $links->nid) .
'<br />
'
;
  }
?>

注意,事实上的连接是由l()函数产生的. l 能够根据配置clean urls或者不是, 能够产生合适的<a href=“link”>连接.

 

最终, 为了显示, 我们需要返回我们产生的内容:
 

<?php
  // check to see if there was any content before returning
  //  the block view
  if ($block_content == '') {  
     // no content from a week ago
     $block['subject'] = 'On This Date';
     $block['content'] = 'Sorry No Content';
     return $block;
  }
 
  // set up the block
  $block = array();
  $block['subject'] = 'On This Date';
  $block['content'] = $block_content;
  return $block;
?>

我们返回一个drupal期望从块函数中返回的有‘subject’和‘content’元素的数组. 如果你不包含上面这些, 那么该块就可能输出不正常.

 

上面的例子假定当一星期前的日期没有内容输出的时候, 你希望块显示“Sorry No Content”. 你也可以简单地选择在没有结果时不显示该块. 要这么做, 你可以:
 

  if ($block_content == '') {  
   

    return;
  }

你可以能注意到这里把层和内容混合在一起是种不好的编程方式. 如果你期望写一个别人能使用的模块, 那么你就需要提供一个更容易调整内容的层结构的方法(特别是对于非程序员). 一个更容易的方式是给你的连接加一个属性, 或者把产生的html代码包围在基于模块级的css的<div>标签内而去掉<br />. 更好的方式是使你的模块“主题化”. 现在我们可以暂时乎略它.

 

安装, 测试和启用模块

此时, 你可以安装你的模块, 它应该能正常工作
INSTALL
把模块内容拷贝到合适的地方就是安装的整个步骤.
启用
以站点管理员身份登录, 到模块管理页面, 通过菜单: Administer » Site building » Modules 或URL:

  • http://example.com/admin/build/modules
  • http://example.com/?q=admin/build/modules

 

当你下拉的时候,你会看到onthisdate模块在‘Other’段中. 通过点击复选框启用它,并保存设置.
配置
为了能让此模块显示一整个块, 单纯启用是不能让它显示的. 你需要到块管理页(Admin >> Site building >> Blocks 或者 pate admin/build/block)启用它.

通过选择‘On This Date’的下拉菜单的某个区域, 你就能启用这个块. 为了取保使用了主题的该块未超出限制得到调整, 在保存了块的区域选项后, 你也许也期望能够进行配置(通过点击“configure“连接), 因此该块就会只在特定的权限下显示在特定的页面位置. 一件值得注意的事情是, 当你以User 1(你安装drupal之后的第一个用户)登录的时候, 你不需要拥有额外的超过“authenticated user“以外权限的角色.
调试
如果你在启用这个模块时得到一个空白页或者一个PHP错误信息(或者在启用并编辑之后), 那么也许你的.module文件会有部分语法错误. 在空白页这种情况下, 你可以通过查看apache日志找到错误信息. 如果你依然无法找到排除错误的方法, 那么只需要删除掉你的模块文件夹(毕竟它无法工作), 这样durpal将回到最初的状态.

产生一个模块配置页

现在我们已经拥有了一个可工作的模块, 我们期望能够更加灵活一点. 如果我们的站点只是随意运行了一断时间, 也许近一年的内容会比近以星期的内容更吸引人. 同样, 如果我们有一个热闹的站点, 我们也许期望显示所有一星期内的内容连接. 因此, 我们需要做一个配置页让管理员能够调整他们期望显示的连接数量, 我们把这个问题放在本文中解决.

创建配置函数
我们需要配置多少个连接能在块中显示, 因此我们需要提供给管理员一个设置连接数目的表单. 第一步要做的事情是使用drupal表单api函数定义一个系统设置表单页面. 因为这是管理页, 所以我们命名这个函数为onthisdate_admin() (或者我们选择另外的名字,但必须以onthisdate_开始)并把它放在onthisdate.module文件里:
 

<?php
function onthisdate_admin() {
  $form = array();
 
  $form['onthisdate_maxdisp'] = array(
    '#type' => 'textfield',
    '#title' => t('Maximum number of links'),
    '#default_value' => variable_get('onthisdate_maxdisp', 3),
    '#size' => 2,
    '#maxlength' => 2,
    '#description' => t("The maximum number of links to display in the block."),
    '#required' => TRUE,
  );
 
  return system_settings_form($form);
}
?>

函数里有几件事情值得我们注意:
  • $form是定义drupal表单api的表单元素的数组. 单个元素代表了一个表单元素; 在上面的例子中, 我们定义了一个需要两个字符的文本输入框, 它的标签说明是“Maximum number of links”, 并且有帮助信息“The maximum number of links to display in the block”.
  • 我们只需要定义实际设置的表单元素 — system_settings_form()函数将会产生表单页, 增加一个提交按钮, 保存这些设置.
  • drupal管理着一些数据库“变量”, 或者设置; 每个设置有一个单独的名字, 所以通常使用模块名做前缀 — 我们的配置称之为’onthisdate_maxdisp’.
  • drupal函数variable_get()被用来获取先前储存的变量设置, 我们会给它一个默认值3, 如果前面没有值的话.
  • 在variable_get()中用到的设置名‘onthisdate_maxdisp’也是$form数组元素的索引. 这很重要, 因为drupal system_settings_form()函数将在表单提交时使用数组索引值作为配置的名字.
  • 所有的显示文本被传递给翻译函数t(), 这样使得别的语言能够在我们的模块中使用.
  • 可以通过drupal表单api参考和drupal表单api快速指南获取更多信息.

 

为了能使用该设置, 我们需要修改hook_block()的实现. 最好的方式是使用db_query_range函数:
 

<?php
  $limitnum = variable_get("onthisdate_maxdisp", 3);
 
  $query = "SELECT nid, title, created FROM " .
           "{node} WHERE created >= %d " .
           "AND created <= %d";
 
  $query_result = db_query_range($query, $start_time, $end_time, 0, $limitnum);
?>

你需要用上面三行替换在onthisdate_block()中的$query和$query_result这些行.

 

增加新页到hook_menu
一旦你通过配置表单创建了函数, 你需要在drupal中定义一个URL到配置页. 通过实现drupal的hook_menu就能做到这个. 在我们的hook_menu实现里, 我们将返回一个有关URL路径, 标题, 和产生页面的函数, 权限的数组.

我们期望只有管理员能够访问这个页面, 因此我们在这里进行权限检查使得drupal能够自己检查相应的权限. 为了使得我们涉及的权限尽量少, 我们使用全局的管理员权限代替单独生成一个权限.

为了实现hook_menu(), 生成一个叫onthisdate_menu()的函数, 然后把它放在onthisdate.module文件中:
 

<?php
function onthisdate_menu() {
 
  $items = array();
 
  $items['admin/settings/onthisdate'] = array(
    'title' => 'On this date module settings',
    'description' => 'Description of your On this date settings page',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('onthisdate_admin'),
    'access arguments' => array('access administration pages'),
    'type' => MENU_NORMAL_ITEM,
   );
 
  return $items;
}
?>

注意,我们所使用的数组索引‘admin/settings/onthisdate’是我们定义的URL, 并且数组元素的索引给出了菜单的标题(“On this date module settings” — 不须以大写字母开始, 其余小写), 更长的描述, 一个返回设置表单的回调函数(‘onthisdate_admin’), 还有访问权限. 你可以通过检索hook_menu文档获取更多内容.

 

在增加了这个以后, 你需要清空菜单缓存, 这样drupal才能识别新的URL. 为了清空这个缓存, 你需要到Administer >> Site Configuration >> Performance拉到页面的最底步, 点击 “Clear cached data” 按钮.

现在你可以测试编辑链接数设置页, 你会注意到会的内容会相应的调整. 到设置页admin/settings/onthisdate 或者 Administer » Site configuration » On this date. 调整连接数字并且保存, 连接的最大值同样会相应的调整.

验证用户输入
尽管我们不需要验证用户输入, 但最好还是这样做. 通过写一个onthisdate_admin_validate函数(在onthisdate.module), 该函数能检查用户输入的值是否大于0. 因为验证函数和form生成函数有相同的名字, 多了一个”_validate“后缀, drupal将会在表单提交的时候自动验证.

产生整页内容

这样我们有了可以工作的块和一个设置页. 块显示了一个最大值数目的连接. 尽管如此, 也许有更多的连接超过了我们所设置的最大值. 因此, 让我们产生一个列出一周前内容的列表页. 这可以分为三个步骤, 在余下的两节中将讨论后面两个步骤.

第一步, 按照这里描述的, 增加一个函数到onthisdate.module文件中, 它将产生一个完全的一周前的内容列表. 我们将命名该函数为onthisdate_all()(我们可以选择另外一个名字, 但必须以“onthisdate_“为起始).

drupal命名中的一个小提示: 如果你想创建一个非常私有的函数(例如没有哪个外部模块依赖它或调用它), 那么就以”your_module_name“做前缀. 如果你的函数是公共的(例如, 其他模块会调用它, 并且你不想经常变更它的参数和行为), 那么就以“your_module_name_“为前缀. 如果你要实现drupal钩子, 那么你的函数命名总是“your_module_name_hookname”. 如果你不想实现一个drupal钩子, 那么就要检查你会不会碰巧刚好选择了一个符合钩子命名的名字.

好, 回到新的onthisdate_all()函数里. 它基本上是我们先前版本的再生, 在那之前, 我们把连接的数量放了进去, 并且我们希望返回基于页的HTML内容. 注意, 我们不必关心HTML头, 页的标题, 菜单, HTML脚等. 我们只需要关心产生的页面的内容部分, drupal和我们的主题将会注意余下部分. 这里是部分函数的实现, 本质上来源于block函数:
 

<?php
function onthisdate_all() {
  // content variable that will be returned for display
  $page_content = '';
 
  // Get today's date
  $today = getdate();
 
  // calculate midnight one week ago
  $start_time = mktime(0, 0, 0, $today['mon'], ($today['mday'] - 7), $today['year']);
 
  // we want items that occur only on the day in question,
  // so calculate 1 day
  $end_time = $start_time + 86400;
  // 60 * 60 * 24 = 86400 seconds in a day
 
  $query = "SELECT nid, title, created FROM " .
           "{node} WHERE created >= '%d' " .
           " AND created <= '%d'";
 
  // get the links (no range limit here)
  $queryResult =  db_query($query, $start_time, $end_time);
  while ($links = db_fetch_object($queryResult)) {
    $page_content .= l($links->title, 'node/'.$links->nid) .
'<br />
'
;
  }
 
  // More coming....
}
?>

象我们先前注意到的那样, 我们在代码里包含了层的信息. 这很糟糕, 应当避免. 在durpal中一个更好的方式是使你的输出“主题化”. 尽管如此, 那个超出了本节范围, 现在我们尽量保持简单, 从而直接包含内容的格式化输出.

 

后面我们将检查是否有内容给我们的用户看. 如果简单的显示空白页, 将会让我们的用户感觉很困惑.
 

<?php
function onthisdate_all() {
 
  // Be sure to include the previous piece of this function!
 
  // check to see if there was any content before
  // returning the page
  if ($page_content == '') {
    // no content from a week ago, let the user know
    $page_content = "No events occurred on this site on this date in history.";
  }
  return $page_content;
}
?>

尽管我们已经有了可以输出一星期前的内容连接, 但我们还指明URL来显示该页. 下面我们就做这件事.

 

让drupal了解新函数

我们刚创建了一个函数来产生一个特殊日子内容连接的页面. 如果我们需要任何人都能看到该页, 下面一件我们要做的事情就是给它一个URL, 通过hook_menu()方法, 我们先前已经通过hook_menu来定义一个设置页面了, 那么我们继续增加另外一个:
 

<?php
function onthisdate_menu() {
 
  $items = array();
 
  //this was created earlier in tutorial 7.
  $items['admin/settings/onthisdate'] = array(
    'title' => 'On this date module settings',
    'description' => 'Description of your On this date settings page',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('onthisdate_admin'),
    'access arguments' => array('access administration pages'),
    'type' => MENU_NORMAL_ITEM,
   );
 
  //this is added for this current tutorial.
  $items['onthisdate'] = array(
    'title' => 'On this date',
    'page callback' => 'onthisdate_all',
    'access arguments' => array('access onthisdate content'),
    'type' => MENU_CALLBACK
  );
 
  return $items;
}
?>

一些要点:
  • 象我们前面提到的hook_menu(), 每个返回的数组中的元素都为我们的drupal站点定义了一个URL. 我们新定义的这个叫‘onthisdate’(例如URL http://example.com/onthisdate 或 http://example.com/?q=onthisdate), 通过给定数组名和页的标题“On this date”.
  • 我们曾说过, 当用户访问这个URL的时候, 函数onthisdate_all()就会产生相应的内容, 通过返回的数组中的‘page_callback’元素.
  • 我们告诉drupal, 只要拥有‘access onthisdate content’权限的所有角色都能访问此页面. 我们将在第三步中处理我们的权限; 你需要访问 Administer >> User management >> Permissions给你站点用户的角色进行授权.
  • 类型MENU_CALLBACK告诉drupal不要在站点的菜单里显示连接, 当访问URL的时候使用这个函数. 而MENU_NORMAL_ITEM将会在系统主菜单中显示连接.
  • 如果你想为你的模块增加更多的页面, 你只需要在hook_menu()的$items数组中增加更多的元素, 然后写页面的显示函数来显示他们.

 

增加一个“更多“连接来显示所有的输入

现在我们已经有了一个页面来实现一个星期前的内容. 让我们在块中增加一个“more“连接连到这里.

在函数onthisdate_block()中, $block[‘subject’]之前增加这些行. 这些行将会在$block_content变量之后增加更多的连接, 在它返回块模块之前:
 

<?php
// add a more link to our page that displays all the links
$options = array( "attributes" => array("title" => t("More events on this day.") ) );
$link = l( t("more"), "onthisdate", $options );
 
$block_content .= "<div class="more-link">" . $link . "</div>";
?>

这将会在block里增加more连接. 注意额外的l()函数的参数. 你可以在数组中增加更多的元素比如‘class’.

 

一个测试模块test_module

test_module.info
 

; $Id$
name = Test Module
description = A Test Module.
core = 6.x
 
package = "Test"
 
version = "6.x-0.1-dev"

test_module.install
<?php
// $Id$
 

 

function test_module_install() {
    drupal_install_schema('test_module');
    db_query("DELETE FROM {cache}");
}
 

function test_module_uninstall() {
    drupal_uninstall_schema('test_module');
}
 

function test_module_schema() {
    $schema['test_module_log'] = array(
        'fields' => array(
            'id' => array('type' => 'serial', 'size' => 'big', 'unsigned' => TRUE, 'not null' => TRUE,
                'description'=> "Log ID"),
 
            'timestamp' => array('type' => 'int', 'not null' => TRUE, 'default' => 0,
                'description'=> "Timestamp (Unix Timestamp, which is limited to values above Jan 1, 1970)"),
            'message' => array('type' => 'text', 'not null' => FALSE,
                'description'=> "Log messages."),  //NOTE:  On MySQL, text fields cannot have default values.
        ),
        'primary key' => array('id') //Don't put a comma after primary key definition, since doing so will cause database errors.
    );
 
    return $schema;
}
?>

test_module.module
<?php
// $Id$

 

 

function test_module_help($path, $arg) {
    //$output = '<p>'.  t("test_module is a simple module to test functions and pages in Drupal");
    //    The line above outputs in ALL admin/module pages
    switch ($path) {
        case "admin/help/test_module":
        $output = '<p>'.  t("test_module is a simple module to test functions and pages in Drupal") .'</p>';
            break;
    }
    return $output;
} // function test_module_help
 

function test_module_perm() {
    return array('administer test_module', 'access test_module content');
} // function test_module_perm()
 

function test_module_menu() {
    $items = array();
 
    //Link to the test_module admin page:
    $items['admin/settings/test_module'] = array(
        'title' => 'Test Module',
        'description' => 'Administer Test Module Messages',
        'page callback' => 'test_module_message',
        'access arguments' => array('administer test_module'),
        'type' => MENU_NORMAL_ITEM,
    );
 
    return $items;
}
 

function test_module_message() {
    $page_content = '';
 
    $page_content .= drupal_get_form('test_module_message_form');
 
    $get_messages = db_query("SELECT * FROM {test_module_log} ORDER BY timestamp DESC");
    if ($get_messages !== false) {
        $page_content .= "<h2>Test Message Log</h2>";
        $row_count = 1;
        $id = 0;
        while($row = db_fetch_array($get_messages)) {
            $page_content .= "<p>";
            foreach ($row as $key=>$value) {
                if ($key == 'id') $id = $value;
                if ($key == 'timestamp') $value = date('F j, Y G:i:s A', $value);
                if ($key == 'message') {
                    if (strpos($value, 'eval:') !== false && $row_count === 1) {
                        $value = trim(preg_replace('/eval:/', '', $value, 1));
                        eval($value);
                        drupal_set_message(t("Executed code:n").strval($value));
                        //Once the "eval:" code is evaluated, remove the "eval:" text to avoid executing the code again.
                        db_query("UPDATE {test_module_log} SET message = '%s' WHERE id = %d", $value, $id);
                    }
                    $page_content .=
"<br />
n"
;
                }
                $page_content .= "<b>".$key."</b> = ".htmlspecialchars(strval($value))."&nbsp;&nbsp;";
            }
            $page_contents .= "</p>n";
            $row_count += 1;
        }
    }
 
    return $page_content;
}
 

function test_module_message_form() {
    $form['test_module_message'] = array(
        '#type' => 'textarea',
        '#title' => t('Message'),
        '#default_value' => variable_get('test_module_message', 'Test Message'),
        '#cols' => 50,
        '#rows' => 5,
        '#description' => t("Enter a test message.  Begin the message with "eval:" to execute PHPcode."),
    );
 
    //Submit button:
    $form['submit'] = array(
        '#type' => 'submit',
        '#value' => t('Save Message'),
    );
 
    return $form;
}
 

function test_module_message_form_validate($form, &$form_state) {
    $test_module_message = $form_state['values']['test_module_message'];
    if (isset($test_module_message)) {
        if (!is_string($test_module_message) || $test_module_message == '') {
            form_set_error('test_module_message', t('Please enter a test message.'));
        }
    }
}
 

function test_module_message_form_submit($form, &$form_state) {
    $test_message = $form_state['values']['test_module_message'];
    $exe_query = db_query("INSERT INTO {test_module_log} (timestamp, message) VALUES(%d, '%s')", time(), $test_message);
 
    $last_id = db_last_insert_id('{test_module_log}','id');
    if ($exe_query !== false) {
        $msg = 'Added message to log: %id';
        $vars = array('%id'=>$last_id);
        watchdog('test_module', $msg, $vars, WATCHDOG_INFO);
        drupal_set_message(t('Added message to log: ').strval($last_id));
    } else {
        $msg = 'Could not add message to log: ';
        $vars = array();
        watchdog('test_module', $msg, $vars, WATCHDOG_ERROR);
        drupal_set_message(t('Could not add message to log.'));
    }
 
    $form_state['redirect'] = 'admin/settings/test_module';
}
?>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值