欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

drupal7 模块开发指南

开始之前

      如果您需要用PHP来查出您站点上的错误,请访问此链接(link is external)。 它将告诉您如何更改Drupal的设置,使drupal显示错误信息。

为您的模块命名

       建立模块的第一步是为它取一个不太长的名字。这个名字将被用在所有的模块文件名和方法名中。所以这个名字必须以字母开头并只能有小写字母和下划线。比如,我们会用"current_posts"作为一个模块名称。

      注意:一定要确保根据以上规则来取名,因为它将被用于模块名和方法名的前缀。当您运行drupal“钩子“时(请参看后面的章节),drupal将只能识别拥有与您模块文件名称相同前缀的钩子。

请注意不要使用与您站点所有主题相同的名称,这样会使drupal混淆。

创建一个模块目录和模块文件

       假如我们决定用"current_posts“的模块名,我们将会在drupal的根目录的以下路径(sites/all/modules /current_posts)创建一个目录。 或者如果您要把自己的自定义模块与其他模块区分开,您也可以选择这个路径(sites/all/modules/custom /current_posts)。然后在sites/all/modules/currents_posts目录创建一个名为 current_posts.module的文件。注意 drupal并不识别扩展名为.php的文件。如果您为模块添加了php的扩展名,那将不会被识别。drupal只识别扩展名为.module的php文 件。

       在Drupal6中,sites/all /modules/是存放自定义模块的理想目录(sites/all/themes/是存放自定义主题的理想目录),因为这个目录存放这所有只与您的站点 相关的模块和主题,在您以后升级您的核心模块时,这些自定义模块和主题不会被改写。或者,如果您有多个子站点共享核心模块,而这个自定义模块只在其中一个 站点工作,那么您可以使用这个路径来存放: sites/您的子站点/modules

模块文件的开头起始于php的起始标签(link is external)“<?php" . 不要把cvs版本控制系统的标签放到模块文件中,drupal应用git的使用规范来实现版本控制。如果coder模块显示错误信息,说明此模块还没有被升级,并不符合drupal的git规范。

我们的模块还不能被执行,它还没有被激活。稍后我们将激活这个模块。

编码标准

       根据drupal编码标准(link is external), 我们在模块中省略"?>"。否则我们可能会遇到服务器实时运行错误。(注意,在本教程中,我们的示例代码中包括了“?>", 这只是为了使代码显示的更好。请在实际应用中忽略"?>")

所有以{模块名}_{方法名}格式命名的方法,都是钩子,都可以被drupal直接调用。其中{方法名}是预定义的方法名后缀。drupal将会调用这些方法来处理数据。所以取一个简单明了的模块名是十分重要的,drupal会知道去哪查找数据。

下一步,我们将踏入钩子的领域。

所有的Drupal模块都有一个"模块名.info"文件,用来储存该模块的元信息。(其实可以理解成模块的描述文件)

该文件的一般格式为:

name = Module Name

description = A description of what your module does.

core = 7.x

 

在本示例中,我们用我们自己的模块名"current_posts"来代替上面例子中的"Module Name". 如果没有.info文件,drupal不会将我们的模块添加进模块列表。下面就是修改后的.info文件内容:

 

name = Current Posts

description = A block module that lists links to recent posts.

core = 7.x

我们把以上内容添加到一个名为current_posts.info的文件中,并把该文件保存在模块的根目录sites/all/modules/current_posts下。

注意:如果您复制以上代码并粘贴到.info文件, 留意description的内容不要有空行,否则本文件将不会正确的读取。

 

.Info文件的细节

 

drupal把所有能够放到.info文件的参数都放到了这个网页(link is external), 请参看。

 

检查

我们现在有了.info文件,要想从drupal文件列表里找到我们的模块,我们只需要一个空的.module文件就可以了。只需要在同样的模块目录下建 立一个.module文件,并在.module文件里添加<?php,我们就会发现我们的模块已经在drupal的模块列表里了。

Drupal模块中的注释语言

在您的模块中添加注释来解释您的模块如何工作,是个很好的习惯。我们需要严格的遵从drupal的注释规则来发布补丁或新模块, 因为drupal用Doxygen来从代码的注释语言中生成文档。请查询doxygen和注释规范(link is external)来获得更多相关信息。接下来的教程对大家是非常有帮助的,即使您也许目前并不需要它。

下面是我们的第一个注释:

<?php
/**
 * @file
 * A block module that displays recent blog and forum posts.
 */
?>

@file告诉您此注释适用与整个文件。注释起始于"/**", 结束于"*/"。按照drupal指南所要求的,drupal模块(link is external)中所有的方法都将用这个注释格式。

运行我们的第一个钩子

       钩子是drupal模块的基本。它们让我们把自己的模块整合到drupal核心模块中去。如果你不知到这些,请参看drupal模块的入门(link is external)

       我们的第一个模块是hook_help. drupal推荐我们在所有模块中应用这个钩子。hook_help为用户提供此模块的帮助信息。要在drupal中运行钩子, 我们要把"hook"替换成我们模块的短名, 并在module文件中用同样的名字建立一个方法。所以,要在我们的示例模块中运行hook_help,我们应该在 current_posts.module文件中建立一个叫做current_post_help()的方法:

<?php
function current_posts_help($path, $arg) {

}
?>

$path变量提供了相关帮助信息的参数:在durpal或模块的什么地方,我们的用户会需要帮助信息。要想处理这个参数的公认的好方法是用 switch语句。 这是drupal非常流行的编写模式。我们的模块将采用下面这个简短的钩子示例, 我们还可以添加注释语言在里面。

<?php
/**
 * Implements hook_help.
 *
 * Displays help and module information.
 *
 * @param path
 *   Which path of the site we're using to display help
 * @param arg
 *   Array that holds the current path as returned from arg() function
 */
function current_posts_help($path, $arg) {
  switch ($path) {
    case "admin/help#current_posts":
      return '<p>'.  t("Displays links to nodes created on this date") .'</p>';
      break;
  }
}
?>

注意我们不应该有"?>"

drupal核心模块会把“admin/help#模块名"链接到drupal的帮助页面"/admin/help/ 或者 ?q=admin/help"。 我们最终将会添加更多的注释语言, 请参看注释语言标准(link is external)。这个方法不仅允许第三方对它的翻译,更加加强了代码的安全性。请参看Localization API(link is external)来获得更多的信息。

检查

       在工具栏点击"模块", 我们便可以在模块列表找到我们的模块。激活模块并点击保存,刷新本页面, 您将看到我们的模块旁边有了一个"help"链接。 点击这个链接,我们就能看见current_post_help钩子返回的内容了。或者您可以在工具栏点击帮助链接(http://example.com/admin/help(link is external)), 您将看见我们的帮助链接被列在帮助列表上。

卸载我们的模块并点击保存。(注意:卸载我们当前未完成的模块是很重要的,我们未完成的模块很容易破坏我们的站点。

声明区块

      模块可以被用来做各种事情, 比如:创建区块(一段简短的内容,通常出现在页面的左右两边);建立特殊的内容类型(就像您现在正在阅读的内容);查询后台信息等等。您也许听说过 block模块--专门被用来创建区块内容(比如菜单),或者node模块--专门用来生成页面的内容(比如日志和论坛)。在我们的例子中,我们所建立的 是一种block模块,因为我们创建了一个区块(block)。

drupal7(link is external)提供了至少8个block钩子可以应用。根据我们例子的需求,我们将用到其中的两个。第一个钩子是hook_block_info()。您可能已经猜到了,这个钩子用来告诉Drupal我们要建立的区块的相关信息。我们将用这个钩子来定义一个能够显示最近新提交的内容的区块。您可以使用任意一个模块提供的block钩子,如此您将需要定义相关模块所需的所有区块,在我们的例子中,我们只需要定义一个区块。

要定义我们自己的区块,我们需要打开current_posts.module文件,并建立一个叫做current_posts_block_info方法, 如下

<?php
/**
 * Implements hook_block_info().
 */
function current_posts_block_info() {
  $blocks['current_posts'] = array(
    'info' => t('Current posts'), //The name that will appear in the block list.
    'cache' => DRUPAL_CACHE_PER_ROLE, //Default
  );
  return $blocks;
}
?>

注意在我们真正的代码中不应该有"?>"在结尾。

代码中的注释已经把这个钩子解释的很充分了。大家可以访问hook_block_info的API页面或者运行一个hook_block_info钩子来了解更多。

我们将把整个队列返回到drupal中。 请注意例子中队列的格式和结构,只是drupal指定的队列结构。队列在PHP中被支持和运行的很流畅,drupal沿用并扩展了队列的用途。

在我们的例子中,唯一一个必须的队列元素是info, 但是hook_block_info还能够把其他的设置具体化。比如在这个里,我们把缓冲存储设置为默认。 请参看hook_block_info(link is external)来获得完整的可用的元素信息。

别忘了返回整个队列到drupal

检查

       访问模块页面并激活Current Posts模块,保存设置。下一步,访问Structure(结构)->Blocks(区块)页面。 在页面的底部,我们应该能够找到被命名为"Current posts"的区块。如果能够找到,说明我们的钩子安装成功了。下一步我们就可以卸载我们的模块并保存设置了。注意:我们必须卸载未完成的模块,因为未完 成的代码可能会导致我们的站点无法工作。

获取信息

       下一步我们将建立一个自定义方法来获取最近在站点发布的内容。当一个节点被建立时,Drupal把它的建立时间存储到数据库中。我们将用这个数据库字段来查询我们所需的信息。我们本可以把这段代码放到我们下一章要讲的钩子中去,但是把这段代码单独放到一个方法里能够是我们的代码更加清晰明了,便于阅读和管理。

我们将称这个方法为current_posts_contents。我们继续遵循将模块短名作为方法名的前缀的规则,跟着我们放一个非drupal钩子名的词在模块短名后面。这个方法起始于如何获得当前时间。下面是代码的第一部分:

<?php
/**
 * Custom content function.
 *
 * Set beginning and end dates, retrieve posts from database
 * saved in that time period.
 *
 * @return
 *   A result set of the targeted posts.
 */
function current_posts_contents(){
  //Get today's date.
  $today = getdate();
  //Calculate the date a week ago.
  $start_time = mktime(0, 0, 0,$today['mon'],($today['mday'] - 7), $today['year']);
  //Get all posts from one week ago to the present.
  $end_time = time();
?>

这段代码用来获得当前时间参数(自从新纪元以来到现在的秒数,请参考mktime(link is external)来了解这里面的含义和格式),然后我们便可以计算出一周前午夜的时间参数。 这段代码是PHP语言编写的,您可以访问php.net(link is external)来获得更多信息。

下一步我们将用drupal的数据库API来获取我们的节点列表。这是我们自定义方法的第二部分。

<?php
  //Use Database API to retrieve current posts.
  $query = db_select('node', 'n')
    ->fields('n', array('nid', 'title', 'created'))
    ->condition('status', 1) //Published.
    ->condition('created', array($start_time, $end_time), 'BETWEEN')
    ->orderBy('created', 'DESC') //Most recent first.
    ->execute();
  return $query; 
}
?>

drupal有自带的非常健全的数据库语言构造器,它运用了特有的面向对象API。我们要注意INSERT,UPDATE,DELETE语句将使用以上的格式来构造。drupal将把这个对象转换为相应数据库语言。对于SELECT语句,虽然我们并不是必须要使用以上格式,但是使用以上格式会加深我们对drupal数据库构语句造器的理解。我们在drupal中见到很多类似的结构.

  1. 我们用db_select命令来构造一个SQL语句,我们将需要把数据库表名和它的简写作为参数传给这个命令。

  2. 这里的field命令用来告诉SQL我们要从那个表(第一个参数)查询那些字段信息(第二个参数中的队列)。

  3. condition命令需要三个参数。第一个参数是要查询的字段。第二个参数数我们要查询的值。第三个参数是比较的条件。如果第三个参数是空,那drupal将使用默认条件--等于“=”。

  4. order命令将根据传进去第一个参数将查询结果排序,第二个参数决定是升序还是降序。

  5. execute命令用来编译和执行我们所构造的SQL语句,并返回结果(一个对象)。

以下是最终的代码:

<?php
/**
 * Custom content function.
 *
 * Set beginning and end dates, retrieve posts from database
 * saved in that time period.
 *
 * @return
 *   A result set of the targeted posts.
 */
function current_posts_contents(){
  //Get today's date.
  $today = getdate();
  //Calculate the date a week ago.
  $start_time = mktime(0, 0, 0,$today['mon'],($today['mday'] - 7), $today['year']);
  //Get all posts from one week ago to the present.
  $end_time = time();

  //Use Database API to retrieve current posts.
  $query = db_select('node', 'n')
    ->fields('n', array('nid', 'title', 'created'))
    ->condition('status', 1) //Published.
    ->condition('created', array($start_time, $end_time), 'BETWEEN')
    ->orderBy('created', 'DESC') //Most recent first.
    ->execute();
  return $query; 
}
?>

请记住在我们的代码中不要包含"?>"

生成区块内容

访问权限检查

这个是代码的第一部分

<?php
function current_posts_block_view($delta = '') {
  switch($delta){
    case 'current_posts':
      $block['subject'] = t('Current posts');
      if(user_access('access content')){
       //Retrieve and process data here.
   }
?>

在把信息显示给用户之前,我们要检查当前的用户是否有权限来查看此信息。在这里,我们执行最基本的权限检查“access content“。 您也可以用Drupal所提供的其他的权限检查,或者建立您自己的权限。要查看drupal权限列表,请访问people>list(或者 http://example.com/admin/people). 在权限的下拉列表中,您将看到您可以使用的权限的机器名。这个列表将包含您站点的所有权限,包括任何自定义模块的权限。那permission页面的权限 名称并不是机器名,所以您不可以把它们用到代码里。

查看可用权限的另一个方法是参看API参考(link is external)。 输入模块名并加上_permission的后缀,比如node_permission。 我们将看到node定义的所有权限。要想知道哪一个权限才是我们需要的没那么容易,比如 access_content是我们在这里要用的,但是它不是block模块所提供的,而是node模块提供的。

在代码中建立链接

这里是下一步的代码

<?php
       //Use our custom function to retrieve data.
        $result = current_posts_contents();
        //Array to contain items for the block to render.
        $items = array();
        //Iterate over the resultset and format as links.
        foreach ($result as $node){
          $items[] = array(
            'data' => l($node->title, 'node/' . $node->nid),
          );
  }
?>

首先,我们在我们自定义的方法中,把内容存储到一个叫作$result的变量中,然后用$item变量来存放要处理的信息。在循环中,我们访问每一个节点,然后根据节点的信息来构建一个链接。

注意,链接是被l()方法所建立的。(小写的L)。第一个参数定义了链接的名字,在这里是节点的名称。第二个参数定义了链接的路径。所有通向节点的路径总会是像"node/#", "#"是节点的ID。l()用这个路径来生成真正的链接。把url转化成相对drupal安装(link is external)目录的URL

主题化我们的信息

drupal的表示层,也被人们熟知为主题层,是一个可以兼容不同主题的系统。每一个主题都可以控制drupal的外观,并且可以控制CSS文件。Drupal主题(link is external)是一个很大的方面,我们甚至可以用一整本书来讨论它。在这里我们只用到了drupal主题的皮毛。我们将会研究主题是如何分析解读队列的。

这里是 current_posts_block_view 方法的最后一部分。

<?php
        if (empty($items)) { //No content in the last week.
          $block['content'] = t('No posts available.'); 
        } else {
          //Pass data through theme function.
          $block['content'] = theme('item_list', array(
            'items' => $items));
        }
      }
  }
  return $block;
}
?>

首先,我们要考虑一种可能性,就是我们可能没有任何内容符合要求。我们的区块应该显示是否有上周新发布的内容。

如果$item变量里面有信息,那么我们将用theme()方法。 第一个参数是一个theme钩子。在这个方法中,drupal提供了很多不同theme钩子供我们使用。请参看默认theme钩子列表(link is external)。我们在这里选择了把信息输出成一个无序的列表,那么我们用到了theme_item_list(link is external)钩子。第二个参数包含了我们要输出的内容。

您在这里也许有些糊涂了。为什么drupal会明白theme_item_list呢?我们不是在使用theme()么?其实在这里theme() 在寻找一个钩子,类似于theme_hookname的格式。如果直接使用这个格式的钩子,那么hookname就充当了theme()的第一个参数。在 这里我们用的是_item_list。它定义了我们要输出的默认的格式。

全部的代码

<?php
/**
 * Implements hook_block_view().
 *
 * Prepares the contents of the block.
 */
function current_posts_block_view($delta = '') {
  switch($delta){
    case 'current_posts':
      $block['subject'] = t('Current posts');
      if(user_access('access content')){
        //Use our custom function to retrieve data.
        $result = current_posts_contents();
        //Array to contain items for the block to render.
        $items = array();
        //Iterate over the resultset and format as links.
        foreach ($result as $node){
          $items[] = array(
            'data' => l($node->title, 'node/' . $node->nid),
          );
        }
        if (empty($items)) { //No content in the last week.
          $block['content'] = t('No posts available.'); 
        }
        else {
          //Pass data through theme function.
          $block['content'] = theme('item_list', array(
            'items' => $items));
        }
      }
  }
  return $block;
}
?>

测试并纠错

激活模块

      点击模块链接(Modules), 或者访问http://example.com/admin/modules, 然后在模块列表中找到other目录。在那里我们应该能够找到'Current posts'模块。选择激活此模块并保存。现在我们应该能看到在模块名的旁边有一个帮助链接。点击这个链接我们就能看到之前我们在模块里写入的帮助信息 了。

激活区块

      下一步,我们访问结构->区块, 或者http://example.com/admin/structure/block。下滑到区块列表的底部。在未激活的区块列表中,我们应该能找到区块名为'Current posts'的区块。把它随便设置在Drupal的一个区域中并保存。访问随便一个其他的页面,我们应该能在我们设定的那个区域找到我们的模块了。恭喜, 您已经完成了您的第一个模块。

纠错

      如果当您激活模块时,您遇到了'白屏'或者PHP 错误,很可能您的module文件代码中有语法错误。请检查您代码中的标点符号是否正确,比如冒号,逗号等。然后检查我们的模块名,方法名,前缀和后缀都 拼写的正确。(一般我们都可以在apache的日志中找到"白屏"的PHP错误。或者您可以选择改变PHP错误的报告等级(link is external))

如果您始终无法找到并改正语法错误,那么您将看到“白屏", 因为drupal将持续的试图把我们的模块在每一个页面运行。最简单的修复这个问题的方法是,删除有问题的模块文件夹,或者把文件夹移出drupal的根 目录。这样drupal将不会试图运行这个模块,站点将恢复工作。

清除缓冲

      如果在您激活了模块之后,您看不到任何变化,很可能是drupal缓冲了很多之前的信息。一般来说,在激活模块时,drupal缓冲不会影响我们。但是在这里我们依然清除缓冲,因为我们尽量把所有纠错方法放到一起,为以后的纠错提供方便和参考。

访问设置configuration->performance,或者http://example.com/admin/config/development/performance, 我们就可以看见清除缓冲的按钮。

添加复杂的功能

生成模块设置页面的准备工作

      现在,我们已经有了一个完全工作的模块。下一步,我们要添加一些复杂的功能在上面。在一个比较复杂的站点, 我们也许并不想把所有最近一周所更新的内容链接都显示给用户。所以,我们要建立一个模块的设置页面,来设置到底要显示多少链接给用户。

注册一个URL

      我们用hook_menu()来定义一个URL, 在这个URL,我们就可以定义我们自己的设置页面了。(原文所说的是用hook_menu()来定义form,这会误导读者。实际上form是在另一个方 法定义的。而hook_menu()只是调用那个方法返回的form,并显示到当前的URL page。 下面是hook_menu()的代码。它将与下一章的代码一起工作。)

<?php
/**
* Implements hook_menu().
*/
function current_posts_menu() {
  $items = array();
  $items['admin/config/content/current_posts'] = array(
    'title' => 'Current posts',
    'description' => 'Configuration for Current posts module',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('current_posts_form'),
    'access arguments' => array('access administration pages'),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}
?>

 

创建配置表单

主要主题描述:表单API

主要函数说明:variable_get(), system_settings_form()

接着,我们将编写current_posts_form()函数,给表单添加元素即给$form数组添加元素。它的模式很像menu的$items变量,它的每个元素名字是它的键,对应的值是特定格式的关联数组。在这个数组中,列出了元素的属性,每个键的名字前面加‘#’。

添加下面的代码到current_posts.module文件。

<?php
/**
 * Page callback: Current posts settings
 *
 * @see current_posts_menu()
 */
function current_posts_form($form, &$form_state) {
  $form['current_posts_max'] = array(
    '#type' => 'textfield',
    '#title' => t('Maximum number of posts'),
    '#default_value' => variable_get('current_posts_max', 3),
    '#size' => 2,
    '#maxlength' => 2,
    '#description' => t('The maximum number of links to display in the block.'),
    '#required' => TRUE,
  );

  return system_settings_form($form);
}
?>

The element attributes

Here we create a text field, with a title of 'Maximum number of posts', and a description, which will appear below the field. Note that both of these strings are passed through the t() function for translation. Size is 2, maximum length is 2. The Form API will automatically validate the maxlength attribute when the form is submitted and throw an error if the length exceeds 2. We have also designated the field as required. The form API will mark it with an asterisk and will throw an error if the form is submitted with no value in the field. See the forms_api_reference(link is external) for the complete list of available elements and properties.

Persistent variables

Certain data, like system settings and user-configurable information, needs to be saved and retrievable for a website to function properly. Drupal accomplishes this through the use of persistent variables. These variables are stored in a database table with keys provided by the implementing module.

The function variable_get() retrieves a persistent variable, and variable_set(), as you might expect, sets one. We use variable_get() above to retrieve the value of our field if it has been set. If not, we specify a default value of 3. variable_get() uses the name of the element, current_posts_max, to find the relevant data.

System settings

     Drupal makes it easy for us to save the form's data with the function system_settings_form(). By using the function in our code, we tell Drupal to provide a submit button and to save data into persistent variables using variable_set(). It will also provide a green confirmation message when data is successfully saved, and a red error message if something went wrong. If you prefer, you can create a submit function yourself, (see Form API Quickstart Guide(link is external)) but for now, we will use this handy shortcut.

Editing the query

We must add two lines of code to our query function, current_posts_contents, one using variable_get() to retrieve the data from our settings form, and the other adding the range function to include this limit in the query. Here's the revised function, with the new lines noted in comments:

<?php
function current_posts_contents() {
  //Get today's date.
  $today = getdate();
  //Calculate midnight a week ago.
  $start_time = mktime(0, 0, 0,$today['mon'],($today['mday'] - 7), $today['year']);
  //Get all posts from one week ago to the present.
  $end_time = time();

//NEW LINE
$max_num = variable_get('current_posts_max', 3);

  //Use Database API to retrieve current posts.
  $query = db_select('node', 'n')
    ->fields('n', array('nid', 'title', 'created'))
    ->condition('status', 1) //Published.
    ->condition('created', array($start_time, $end_time), 'BETWEEN')
    ->orderBy('created', 'DESC') //Most recent first.
    ->range(0, $max_num) //NEW LINE
    ->execute();
  return $query;
}
?>

Using variable_get(), we save the configuration setting into the $max_num variable or assign a default of 3. Then we add the range method to our select query. That method's first argument is set to zero to specify starting at the beginning of the set. The second argument, $max_num, determines how many records to return.

Add configuration button in Modules page

In your current_posts.info file add the following line to create a linked button to your configuration page in the modules page.
 

<?php
name = Current Posts
description = A block module that lists links to recent posts.
core = 7.x

; NEW LINE
configure = admin/config/content/current_posts
?>

Check

Go ahead and again enable your module. Then you will need to clear the menu cache, so that Drupal will recognize the new URL. (Drupal caches a lot of data, including a list of all the URLs it recognizes.) To clear the cache, go to Configuration > Performance or http://example.com/admin/config/development/performance, and click the Clear all cachesbutton.

Now you can test the settings form. Navigate to it: Configuration > Content authoring > Current posts or http://example.com/admin/config/content/current_posts. Adjust the number of links and save the configuration. The maximum number of links in the block should adjust accordingly. See how system_settings_form() has added a submit button and gives you a confirmation message when you have successfully saved.

Please note: be sure to disable your module before continuing.

 

检查数据

       表格的API提供了出色的默认表格数据检查机制,但是也允许我们建立自己的数据检查方法。在这里,我们需要确保用户输入的数字大于0。检查数据的方 法和validate钩子很相似,只是用表格的ID代替了模块名。所以我们的数据核实方法应该叫做 current_posts_form_validate($form, &$form_state)。不要把这个与validate钩子想混淆,validate钩子是系统钩子,而我们的方法是自己建立的某一个表格的 数据检查方法。

请注意$form_state队列变量,这个参数在表格方法和数据检查方法中都要用到。$form_state队列中含有表格在每个状态中所包含的 信息和当前状态。用户所输入的数据储存在$_POST变量中,先要被检查是否符合表格的结构,然后会被传送到表格检查方法和提交方法。队列的“value “关键字默认储存了用户所输入的信息。请参看$form_state()的关键字列表drupal_build_form()(link is external)

添加下面的方法到您的current_posts.module模块文件中:

<?php
/**
* Implements validation from the Form API.
*
* @param $form
*   A structured array containing the elements and properties of the form.
* @param $form_state
*   An array that stores information about the form's current state
*   during processing.
*/
function current_posts_form_validate($form, &$form_state){
  $max_num = $form_state['values']['current_posts_max'];
  if (!ctype_digit($max_num)){
    form_set_error('current_posts_max', t('You must enter a number for the maximum number of posts to display.'));
  }
}
?>

 

测试数据

       首先,我们从$form_state队列中获取模块设置表格的数据并把他们存在一个变量中。然后我们便可以查看这是否是一个数字。如果不是,我们就 用form_set_error来返回一个错误信息。这个方法的第一个参数表明您的表格的field名,是哪一个field出现了错误。第二个参数是您想 向用户显示的信息内容。这个信息是用红色的框包围起来的。

如果用户所输入的信息通过了第一个检查,那么我们要检查它是不是一个正数。如果不是,我们还是用form_set_error来显示错误信息。注意错误信息的string是要用t()方法来翻译一下的。

检查

       激活您的模块并访问设置页面。输入一些数字来看看我们的数据检查方法是不是工作。如果您要添加一些代码,请确保您的模块是未激活的。

为页面建立自定义的权限

       目前为止,我们已经有了一个区块和一个设置表格。区块显示了所能够显示的最多的链接。但是,也许存在更多的链接,我们的区块的大小不够显示。所以我们需要一个页面来显示更多的最近一周所新建的内容。

自定义权限

       首先,我们用hook_permission()建立一个定义权限。这个钩子所定义的权限可以在people->Permission页面设 置。(http://example.com/admin/people/permissions)只有那些拥有这个权限的用户才能访问我们将要建立的页 面

添加下列代码到我们的模块文件:

<?php
/**
* Implements hook_permission().
*/
function current_posts_permission(){
  return array(
    'access current_posts content' => array(
      'title' => t('Access content for the Current posts module'),
    )
  );
}
?>

这个钩子的定义严格遵循了Drupal队列的模式。重点在于机器可读的钩子名,他应该和我们要建立的hook_menu()名相同。'title'定义了这个权限的名称,它将出现在权限管理页面。这个名称应该被t()方法所翻译。请参看hook_permission()(link is external)来查询所有可用的选项参数。

注册URL并为页面方法命名

       我们将编辑current_posts_menu()钩子,来建立一个路径和新页面的名称。下面是drupal命名的一个快速提示:如果我们要建立 的私有的方法,别的模块基本不会用到的,那么我们用下划线来作为方法名的开头,比如_your_module_name_。如果我们的方法是公开的,其他 模块会频繁的用到它,我们也不会频繁的更改它的参数,那么我们不用下划线作为名字的开头,比如your_module_name_。如果我们要执行的是一 个钩子,那么我们必须遵循your_module_name_hookname的格式。所以,如果我们没有执行一个钩子,我们已定要检查我们的命名没有恰 巧符合一个钩子的格式,否则系统将认为这是一个钩子。

页面的callback是一个非常好的私有方法的例子。所以我们的命名起始与一个下划线+模块名+'page'。把下面的代码作为队列的第二个元素 添加到我们的current_posts_menu()方法。确保我们 return $items是方法的最后一行, 记住不要包含php的开始和结束符。

<?php
  $items['current_posts'] = array(
    'title' => 'Current posts',
    'page callback' => '_current_posts_page',
    'access arguments' => array('access current_posts content'),
    'type' => MENU_NORMAL_ITEM, //Will appear in Navigation menu.
  );
?>

把这个menu item和前一个menu item做对比。前一个item需要有description作为设置页面的描述。在这里,这是不重要的。因为我们将建立的页面方法不会调用 drupal_get_form,我们并没有任何页面参数要传进去。新建的权限将成为我们的access argument。因为我们没有用admin/config路径,所以我们的页面链接将出现在Navigation menu。

检查

       激活我们的模块,并检查Navigation menu。我们应该能看到Current posts的链接。如果看不到,请试着清空缓冲,然后再试试。因为我们还没有编写页面方法,所以这个链接将引导我们到一个空白页面,或者错误信息。在继续 编写我们的模块前,请记住卸载当前模块。

启用数据库命令

       我们要新建的页面方法基本会与 current_posts_block_view的功能相同,它将从我们要建立的current_posts_contents方法中获得数据。所以我 们需要在current_posts_contents方法中来读取数据库并获得数据。我们所需要做的就是编辑这个方法,调用一些数据库命令,然后把返回 的信息传给页面。

下面是我们要编辑的代码:

<?php
function current_posts_contents($display){   //$display argument is new.
  //Get today's date.
  $today = getdate();
  //Calculate midnight a week ago.
  $start_time = mktime(0, 0, 0,$today['mon'],($today['mday'] - 7), $today['year']);
  //Get all posts from one week ago to the present.
  $end_time = time();
 
  $max_num = variable_get('current_posts_max', 3);
 
  //Use Database API to retrieve current posts.
  $query = db_select('node', 'n')
    ->fields('n', array('nid', 'title', 'created'))
    ->condition('status', 1) //Published.
    ->condition('created', array($start_time, $end_time), 'BETWEEN')
    ->orderBy('created', 'DESC'); //Most recent first. Query paused here.

   if ($display == 'block'){
  // Restrict the range if called with 'block' argument.
    $query->range(0, $max_num);
  } //Now proceeds to execute().
  //If called by page, query proceeds directly to execute().
 
  return $query->execute();
}
?>

首先,我们把$display这个变量假如到方法中。然后在数据库命令构建结束后,我们来判断这个变量,来决定如何继续构建和运行数据库命令。如果 这个变量是'block', 表明是block在请求信息,那么我们则限制选择的范围,因为block能显示的条目有限。如果变量参数是'page'。表明是page页面在请求信息, 那么我们将不对数据库选择范围进行限制,页面将显示所有信息。

编辑current_posts_block_view

如果我们要想我们的代码像current_posts_block_view一样工作,必须要在block区块的显示代码中加入类似下面的代码来调用我们所建立的方法。

<?php
$result = current_posts_contents('block');
?>

要想代码工作得像block_view一样,只需把‘block’作为参数传到方法中。

 

 

主题化页面

现在我们将要编写一个页面方法,来把页面内容返回到我们之前建立的current_posts_menu()钩子中去。下面是代码的第一部分:

<?php
/**
* Custom page callback function, declared in current_posts_menu().
*/
function _current_posts_page() {
  $result = current_posts_contents('page');
  //Array to contain items for the page to render.
  $items = array();
  //Iterate over the resultset and format as links.
  foreach ($result as $node) {
    $items[] = array(
    'data' => l($node->title, 'node/' . $node->nid),
    );
  }
?>

是不是看上去跟之前所编写的代码很相似?我们调用之前建立的方法来读取数据库信息,传入参数'page‘, 这样程序就会返回所有的信息。然后我们就可以遍历返回的信息,为美一个节点生成链接。

解析队列

       下面我们将进入Drupal的主题系统来把我们的队列内容输出成特定的格式。drupal7(link is external)总是把页面的内容储存在队列里,然后主题系统将才把内容打印输出。这样我们就尽可能长时间的可以把内容当作数据来处理,而不是HTML代码。请参看更完整的解释 Render Arrays in Drupal 7(link is external)下面的代码包含了我们第一个可解析的队列:

<?php
  if (empty($items)) { //No content in the last week.
    $page_array['current_posts_arguments'] = array(
      //Title serves as page subtitle
      '#title' => t('All posts from the last week'),
      '#markup' => t('No posts available.'),
    );
    return $page_array;
  }
?>

这段代码跟current_posts_block_view()方法(当没有信息显示时)的显示效果一样。我们建立一个队列变量$page_array。变量名只要不合其他变量方法名冲突,是无关紧要的。为了编码的一致性,我们沿用模块名为变量名的前缀。

子队列严格的遵循可解析队列的格式。元素名前缀为"#",应用解析队列API中的元素。#title元素定义了这个队列包含的内容的标题。页面的标 题是由curent_posts_menu()定义的,所以这里定义的是子标题。#markup是最简单的元素,在这里提供一个您要显示的string。 您也可以用HTML代码。只要用t()方法来翻译他们即可。

您可能发现这个方法看上去与我们之前建立的设置表格的方法相似。他们都用‘#’来当作元素的前缀。form API总是用render API的元素,但是我们之前没有很好的把这一部分写到文档里。现在在drupal7,我们能够从form API索引(link is external)找到所有form API所用到的元素。

主题钩子的建议

下面是代码的最后一部分:

<?php
    
  else {
    $page_array['current_posts_arguments'] = array(
      '#title' => t('All posts from the last week'),
      '#items' => $items,
      //Theme hook with suggestion.
      '#theme' => 'item_list__current_posts',
    );
    return $page_array;
  }
}
?>

我们又用到了#title元素。跟着的两行我们调用theme_item_list()来解析队列。在 current_posts_block_view()方法中,我们用到了下面的代码:theme('item_list', array('items' => $items));,它们的功能是一样的。$items变量包含了一个链接的队列,被付给了#items元素,#theme元素定义了我们将用 drupal默认的item lists作为主题输出格式。

在这里我们注意到一个细节。我们在主题钩子调用是用了两个下划线加模块名。这样是为了给其他主题编写者更大的自由。如果我们在这里只用默认的主题钩子,其他主题编写者做的任何改动将被应用于这个钩子,不只是我们的列表。在这里,主题将只修改我们的本模块的输出格式。

两个下划线是drupal认识的格式,它表示这是一个建议的主题钩子。如果您需要,您可以编写任意的有双下划线的建议。drupal将优先查找建议的主题钩子,然后才是默认的钩子。本模块并不使用建议的主题钩子,但是这是可以被加入的。

检查

是时候检查我们的模块了。 激活我们的模块并点击Navigation目录的链接。如果我们是未登录状态或者没有权限访问,我们将无法看到链接。

Adding a 'More' link

Main topic described: Block system, Render arrays, Menu system
Main function described: drupal_set_title()

With this last addition to the module, we will pull together what you have learned about the block system, the menu system, and render arrays, and throw in a workaround for a minor bug in the current version of Drupal 7.

It may have occurred to you that access to this page belongs not in the 'Navigation' menu, but through a 'More' link at the bottom of the 'Current posts' block. That was the plan all along, but we started with a menu link to show how it works.

If you looked at the Default theme implementations(link is external) referenced back in Generating block content(link is external), you may have noticed the theme_more_link(link is external) theme hook. We'll use that, along with a theme hook suggestion, to theme the 'More' link for our block.

Child render elements

We'll start by converting the call to theme_item_list into a render array as we did in the page function. We'll also make it a child of block['content'] to allow for the 'More' link as a sibling. Here's the revised code for the last section of current_posts_block_view:

<?php
else {
  //Pass data through theme function.
  $block['content']['posts'] = array(
    '#theme' => 'item_list__current_posts__block',
    '#items' => $items,
  );
?>

We move the item list code to $block['content']['posts'], making it a child element. You can name the child element whatever you like, as long as it doesn't start with a hash mark (#). We convert the call to theme_item_list into a render array, then add the __current_posts theme hook suggestion. This we follow with a second suggestion in case a themer wants to render the lists in the block and page differently.

Here's new code for the 'More' link to add directly after the last listing:

<?php
  //Add a link to the page for more entries.
  $block['content']['more'] = array(
    '#theme' => 'more_link__current_posts',
    '#url' => 'current_posts',
    '#title' => t('See the full list of current posts.'),
  );
}
?>

Here we make the 'More' link a sibling to the posts array and provide the two parameters this theme hook requires, the path and title. The title provides text which will appear as a tooltip when the mouse hovers over the link. We give this theme hook a suggestion as well.

Edit current_posts_menu()

At this point, we have links to the page both in our block and in the 'Navigation' menu. We don't want a link in the menu, so we'll edit current_posts_menu() to take it off. All you need to do is change the type attribute for the page item. Delete MENU_NORMAL_ITEM and replace it with MENU_CALLBACK. This type provides a path and attributes only, with no menu link. Should you have your module enabled, you'll need to disable then re-enable it for this change to take effect.

Page title fix

Enable your module and follow the 'More' link in the block, then note the page title. 'Home'? Not what we had in mind. This is caused by a bug in the first version of Drupal 7. (If you see the 'Current posts' title, you are using a later version that is not in release at the time of this writing.) There is a patch in the works, but we need to code for the current release. Even when the bug is fixed, we can't be certain that our module won't be used on an installation that has not been updated.

The workaround is to use the function drupal_set_title(). Add the following to the beginning of your _current_posts_page() function:

<?php
  drupal_set_title('Current posts');
?>

With this addition, you can be sure the correct title will appear on the page, no matter which version of Drupal 7 your module is installed in.

Check

Check your module functionality one final time. If you have been following along, the last thing to check is the corrected page title, 'Current posts'. If you have problems getting your code additions to appear, try clearing the caches or disabling then re-enabling your module.

View the code

You can view all the code for the .module file here: current_posts.module(link is external)

用SimpleTest测试

Drupal 7的内核包含了测试的功能。Drupal 的SimpleTest模块是基于SimpleTest PHP Library(link is external)的。如果您已经很熟系这个框架了,那么您将能更轻松的理解如何把这个框架应用到Drupal中。

Drupal测试相比于元素测试,更偏向于功能测试。功能测试将不会检查个体方法或代码,它偏向于检查测试整体的交互界面功能。这个方法更是用于Drupal。我们既要开始编写一个测试了,我们的测试程序将检查我们模块的四个钩子是不是能够如期的运行。

编写一个好的测试代码的一个挑战是您是否清楚哪些是您需要测试的,您从测试代码中所获得信息并不会告诉您我们的模块到底是如何工作的。这些测试将把您引向一个测试环境其中包含一些假定。想讨论如何设计您的测试程序,请访问SimpleTest教程的Figuring out what we need to test(link is external)部分。当您熟悉了这个基本部分之后,您将能够分析和理解drupal.org网站的SimpleTest部分其他的教程和示例。

建立测试文件

首先,我们将建立并注册一个测试文档。测试文档的命名必须遵从{modulename}.test的格式。我们在current_posts的目录下建立一个名叫current_posts.test的文件。注意我们的文件只包括php的起始符,并没有结束符。

和数据库API一样,SimpleTest的框架也使用面向对象代码。测试被编写在类里面。就像在Telling Drupal about your module(link is external)里面讲到的,所有模块必须在.info文件中把自己的类声明。我们把下面的代码添加到current_posts.info文件的结尾:

files[] = current_posts.test

类的继承

接着,我们将添加一个区块,并开始编写测数代码。将下面的代码添加到我们的测试文件中:

<?php
/**
* @file
* Tests for the Current posts module
*/

class CurrentPostsTestCase extends DrupalWebTestCase{
  public static function getInfo(){
    return array(
      'name' => 'Current posts module functionality',
      'description' => 'Tests hooks in the Current posts module',
      'group' => 'Current posts',
    );
  }
?>

我们将把我们的测试编写成CurrentPostsTestCase类的方法。注意大写的标准,类名中的每一个单词首字母必须大写。在类的方法名中,第一个单词小写,后面的单词首字母大写。比如getInfo()。下划线和减号是不能用在类名和方法名中的。

基本类DrupalWebTestCase为我们提供了一个测试的基本框架。它建立了一个内部的drupal站点和浏览器之间的联系。请参看API functions(link is external)来了解它的全部功能。SimpleTest还提供了一个类DrupalUnitTestCase,作为元素测试, 这并不需要drupal bootstrap的支持,换句话说,我们不需要drupal来运行元素测试。

getInfo()方法对drupal的SimpleTest来说是必须的。我们必须有这个方法才能运行。队列里面的元素定义的我们的测试在drupal中的显示。

setUp()方法

下面的方法看上去很简单:

<?php
  public function setUp(){
    parent::setUp('current_posts');
  }
?>

这个方法允许我们调用父类中的一个方法来进行必要的设置,我们可以建立一个新用户,给它一些权限,把用户登录。我们在这里要做的就是把我们要安装的 模块作为一个参数来传进去。在这里,我们传的是“current_posts“。没有这个方法,我们也可以运行我们的测试,但是一般来讲,我们都需要这个 方法来进行一些设置。

第一个测试和module_invoke()

下一步,添加下面的代码:

<?php
  public function testPermission(){
    $data = module_invoke('current_posts', 'permission');
?>

我们将开始测试我们最简单的一个钩子current_posts_permission()。我们建立一个变量来记录 module_invoke()返回的值。这个方法需要两个参数:模块名和钩子名。当测试运行时,它将等同于运行 current_posts_permission()。每一个测试都开始与这个方法的调用,

假定

当我们激活了一个钩子,假定assertions(link is external)将 执行实际的测试。一组方法将检查是否符合条件并返回一个布尔值。如果是TRUE,测试将通过。反之,测试将失败。它将遵 循$this->assertion(condition(s), message)的格式,message是需要被t()方法翻译的,它将在测试运行时显示那一部分被测试了/

把下面的钩子测试方法添加到testPermission()方法:

<?php
    $this->assertTrue(is_array($data), t('Permission hook returns array.'));
  
    $this->assertTrue(array_key_exists('access current_posts content', $data),
    t('Permission is as expected.'));
  }
?>

assertTrue 将检查第一个参数是否为TRUE,第二个参数是测试运行结束后将要显示的信息。我们要检查第一个方法是不是返回一个队列,如果我们权限的名字是队列中的 key,那么信息将显示我需要的结果。这就是我们测试current_posts_permission()的方法。

测试equality

下面我们来测试menu钩子。添加下面的代码到测试文档:

<?php
  public function testMenu(){
    $items = module_invoke('current_posts', 'menu');
  
    $this->assertEqual(2, count($items), t('Two items in menu.'));
  
    $this->assertIdentical($items['admin/config/content/current_posts']['title'],
    $items['current_posts']['title'], t('Titles are identical.'));
  }
?>

在激活了模块之后,我们用到另外两个假定。assertEqual()就跟==的作用一样。它假设两个条件是相等的。在这里代表所建立的menu等于2。count($items)会返回menu的个数。

assertIdentical 就像===一样。我们可以用它来检查两个menu的title是否一模一样。

另一个假定

下面是另一个方法的代码:

<?php
  public function testBlockView(){
    $data = module_invoke('current_posts', 'block_view', 'current_posts');
       
    $this->assertEqual(t('Current posts'), $data['subject'], t('Subject is as expected.'));
  
    $this->assertNotNull($data['content'], t('Block contains data.'));
  }
?>

在我们要测试current_posts_block_view()的方法中,我们在module_invoke()中有了第三个参数。这个参数告 诉方法在特定时候要调用current_posts方法。我们首先用equality来检查区块的title。我们要小心类似的测试。如果我们改动了区块 的title而没有改相应的测试代码,那么这个测试将break。这个方法使用与测试那些不容易改变的变量。

下一步我们用assertNotNull()来检查第一个参数是不是空。我们希望变量中包含信息。

2个负面的假设

添加下面的代码来测试current_posts_block_info():

<?php
  public function testBlockInfo(){
    $info = module_invoke('current_posts', 'block_info');
  
    $this->assertNotEqual('DRUPAL_CACHE_PER_USER', $info['current_posts']['cache'],
    t('Cache is not set to DRUPAL_CACHE_PER_USER'));
  
    $this->assertFalse(count($info) > 1, t('No more than one block defined.'));
  }
?>

assertNotEqual()相当与假设不等于。将第一个已知的变量和第二个未知的测试变量进行比较。这里我们要检查缓冲是不是没有被设置成DRUPAL_CACHE_PER_USER。注意,在这里我们用了一个附加的队列元素来解析测试变量。

在assertFalse中,我们用一个公式来检查这个方法是不是只定义了一个区块。

激活两个钩子

把下面的代码方法加到我们的测试文件中来完成测试的最后一部分:

<?php
  public function testBlock(){
    $info = module_invoke('current_posts', 'block_info');
    $data = module_invoke('current_posts', 'block_view', 'current_posts');
    
    $this->assertIdentical($info['current_posts']['info'], $data['subject'], t('Block list
    name and subject are identical.'));
  }
}
?>

在这里我们激活两个区块方法来比较每个方法中的变量。我们用到了===假定,这是我们测试的最后一部分。

检查

SimpleTest是核心模块的一部分,但是并不在默认安装的模块内。请访问模块页面(http://example.com/admin/modules(link is external)), 在核心模块的部分查找一个叫做’Testing‘的模块并激活它。在设置页面确保‘Provide verbose information when running tests‘别选择。我们从测试中得到越多的信息越好。

现在激活我们的Current posts模块。如果模块已经激活,那么卸载并重新激活一遍。访问‘Testing’页面(http://example.com/admin/config/development/testing(link is external)),并在测试列表中查找'Current posts' 。如果您已经运行了测试,您需要在页面的中下方点击'Clean environment'来让我们的测试显示正常。

当一切准备就绪,选择'Current posts'测试的选项并点击'Run tests'按钮。我们将看见一个进度条来显示测试的进度。请耐心,测试模块将用较长时间来建立一个虚拟环境并运行所有的测试。注意,进度条下面的文字将 和我们在代码中getInfo()方法的name元素吻合。

当测试结束,我们将看见一个提示信息来告诉我们测试通过,失败,或者有错误抛出。想查看具体信息,请点击测试名下方的链接'Current posts module functionality‘。绿色背景的’pass'表示测试的结果和我们希望的结果相同。红色背景的‘fail'表示结果不想同。黄色背景 的’exception'表示测试外有异常抛出,比如PHP警告。如果遇到fail或者exception,请检查您的代码,并重新测试知道通过为止。

查看代码

您可以查看这个测试的代码文件:current_posts.test(link is external)

Patch练习

恭喜,现在您已经能够编写简单的模块并测试了。但是在现实世界中,我们基本不会有机会从零开始编写自己的模块,多数情况下,Drupal已经有一个模块能满足或者接近满足我们的需要。所以如果您要为一个模块提供新的功能或者纠正错误,您共享到整个社区。

在这部分,您将有机会来练习为一个模块建立patch和分析别人为您的模块编写的patch。比如说,我们的模块发布了,但是没有‘more'链 接,我们可以提供这个功能。我们将用一个特殊的模块'Current content'代替'Current posts'。您可以把这个模块建立到http://example.com/sites/all/modules路径,来避免与我们之前写的 'Current posts'模块产生冲突。

我们的任务是用drupal默认的版本控制系统GIT, 来复制工作区的根目录,建立子分支,在添加了‘more'功能后,提交一个patch。如果您还能想出更多的功能,您也可以一起添加到新功能里。然后把您 的patch提交到'Current content' 的报错队列中。下一步,审查在队列中由别人编写的在其他子分支的patch。如果这个patch工作无误,那么我们将把他的状态改为‘审核并测试通过’ (RTBC)。(大多数的patch很难达到这个状态,所以在提交patch前,请仔细审查您的patch)如果有人在提交patch的同时还审核 patch,那么您的patch也将会被审核。

连接到GIT

Drupal开发社区用GIT来管理drupal所有版本的所有文件。如果您还不熟悉GIT,请参阅一些出色的文档,从Introduction to Git(link is external)开始。您将需要连接到GIT到您的电脑,请参看Installing Git(link is external)

要应用drupal.org的GIT,您必须在drupal.org有一个账户,并在您的资料编辑页面接受'Git access agreement'。请参看Obtaining Git access(link is external)来获得更详细的说明,然后到Identifying yourself to Git(link is external)来完成您的设置。

复制工作区根目录

现在我们可以开始建立patch了。首先,访问Current content(link is external)的 开发沙盘。在页面的顶部,查看'Version control'链接,然后在您的电脑上启动终端,然后输入命令来复制工作区。(本部分的说明是基于命令行的,如果您用的是图形界面,您需要把命令转 化。)如果您吧代码复制到了http://example.com/sites/all/modules目录,您可以检查功能并运行测试,

建立patch

在GIT中,不同的路径可能会链接到同一个终点。这就是建立patch的真谛。如果您已经有了您自己的流程,只是想练习一下,请继续您自己的流程。 在这里,我们将用子分支来联系,这样我们就可以建立并审查patch,而不会影响到模块的源代码了。如果您对以上的说明有意问,请参看下面的其他选择。

'Current content'(link is external)队列中,在‘新功能需求’目录下,建立一个新issue。请注意issue号,这将是您issue的url,用下面的命令建立一个子分支:

git checkout -b more_patch-[issue-number]

checkout告诉GIT你在改变一个子分支。-b的意思是您在建立的是一个新的子分支,后面跟着是名字。您可以随便为子分支命名,但是在这里我们建议用issue号,这样就不会于其他issue混淆。我们在这里把batch加到名字里来与我们将审核的batch区分。

现在您可以修改.module文件了,您可以选用任意的编辑器。只要您是在子分支上工作,您的修改就不会影响到主分支的代码。要添加‘more'链 接,您需要修改menu方法,page方法,和block view方法中的theme_item_list,就像我们在之前的章节(link is external)中做的那样。请在您的开发站点中查询相关的代码,让你后您可以运行'Current content'的测试,应该都可以通过。

当您对您的修改满意时,您就可以把修改上传到GIT。在命令行(或者图形界面)中,运行下面的命令:

git status

status命令会告诉您您目前处于patch的子分支上,它还会告诉您您的.module文件已经被修改,但是还没有上传。下一个命令:

git diff

这个命令对您的修改做了最后的检查,来确保您已经包含了所有需要上传的。如果您不小心包含了空格在里面,GIT将把它标出来。确保您把空格都删除,确保所有格式和修改都是正确的,我们的目标是用一次上传就把所有的修改完成。

当您准备好上传您的修改。-A参数将把所有的修改上传到子分支。包括新文件,修改了的文件,和删除了的文件:

git add -A

如果您在这时候运行git status, GIT将会把您的文件放到'Changes to be committed'目录下,现在实施修改:

git commit -m "Issue #[issue number] by [your drupal.org username]: Added a 'More' link"

commit信息严格的遵循drupal的格式,请参看Commit messages - providing history and credit(link is external)来查询详细的解释。下一步为了防止以外的更改,我们获取最新的代码:

git fetch origin

GIT将提示您正在从drupal.org的'Current content'工作区获取并解压对象。下一步在刚获取的文件中做我们刚才做过的更改:

git rebase origin/master

您将收到信息,告诉您GIT已经从头修改分支上的文件,现在您已经准备好建立您的patch文件了。

建立patch文件

我们有两种方法来建立patch文件:

  • 1.只上传更改一次。如果您在一次commit中完成了所有更改,那么您可以用git format-patch命令来建立patch文件并包含您的信息: git format-patch origin/master --stdout > added_more_link-[issue number]-[comment number].patch

确保您没有井号在文件名里,不然文件将不会被接受。

  • 2.多次长传。如果您上传了多次,那么您可以用git diff: git diff origin/master > added_more_link-[issue number]-[comment number].patch

当然,如果您愿意,您夜可以用别的名字。如果您是为core模块写的patch,那这里的将会用core模块的版本号好作为您patch的版本号。如果是其他模块,版本号可能会不同。(如果您在作出注释之前就已经建立了patch,那么您可以推算出注释号。)

当您运行了这个命令,GIT将提交这个patch,但是不会显示任何信息。您将在您当前的工作区的根目录下找到patch文件。(这个工作区只有一 个文件夹,如果有多层子文件夹,那么这个patch文件将会被保存在最上层的文件夹。)打开这个文件,并检查您的修改。如果您用的是git format-patch命令,这个patch文件将起始于井号,作者的名字,邮件地址,建立时间和上传信息。如果您用的是git diff命令,这个patch文件将用diff作为起始。如果您只是为了‘more’链接做了修改,那么您的patch文件应该包括三部分。每部分将用行 号和修改的方法作为起始。

现在返回到'Current content'的问题队列,上传您的patch,把状态改成‘Needs review‘。

如果您现在运行git status命令,您将发现您的patch文件已经被包括了。为了您不会重复上传这个patch文件,请用下面的命令移除文件:

rm added_more_link-[issue number]-[comment number].patch

审核patch

现在我们已经建立了一个patch,是时候审核别人的patch了。首先选择一个patch,并点击它名字上的链接来浏览。把issue号记录下来。

然后回到命令行,进入current_content目录,检查状态:

git status

如果您不在主工作区,请用下面的命令:

git checkout master

下面要确保您在用最新的版本的代码:

git pull

为审核这个patch建立一个新的子分支:

git checkout -b more_review-[issue number]

有集中不同的方法来下载和应用这个patch。如果您要用curl,请查看这个链接(link is external)在 'Reviewing patches'下面的内容。我们在这里将用Unix命令wget:

wget http://drupal.org/files/issues/[patchname].patch
git apply -v [patchname].patch

当您下载这个patch时,您将获得很多数据,当您用-v参数时,您将得到一个信息来告诉您是否已经把这个patch应用成功。

如果您现在运行git status,您将看到这个patch已经被包含了。为了您在将来不会重复上传这个patch,请用下面的Unix rm命令来移除此文件:

rm [patchname].patch

检查代码的变化:

git diff

您也可以用其他编辑器查看文件。

现在检查patch。因为现在的sandboxes已经不包括testbot的功能,所以您需要在您的站点上用SimpleTest来测试。请留意您所测试的,哪些工作,哪些不工作。并在issue中写明。要查看如何审核patch的细节,请访问Reviewing patches(link is external)

如果在您测试中发现什么问题,请把issue的状态改为'Needs work'。如果工作无误,请把状态改成RTBC。注意,在真是的issue队列中,一般需要2人以上来认证patch和把状态改为RTBC。请参看链接(link is external)来了解如何适当的改变issue状态。

总结

我们终于到了这个教程的结尾。如果您是从头做下来的话,您应该已经熟悉了钩子系统,区块系统,目录系统,数据库系统,表格系统,解析队列,页面方法,一些drupal方法,SimpleTest,和用GIT提交patch。

下一步做什么呢?看一下 Working with the Drupal AP(link is external)Examples for Developers(link is external), 那些是您所感兴趣的。

来自 http://drupalchina.cn/node/5640

普通分类: