3 module文件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们在文件的开始处使用PHP的开始标签,接着添加了一段简洁的注释:


<?php

 

/**

 * @file

 * 让用户为区块添加一个更多链接.

 *

 * 在区块的配置页面,允许用户输入更多链接,在区块显示的时候,显示一个更多链接.

 */

 

    首先需要注意的是,PHP开始标签“<?php”的前面不要有任何空格以及任何字符,否则有可能带来不必要的麻烦,举个例子userpoints_nc的module文件中,<?php”的前面带有了两个空格,结果导致RSS输出不能正常工作(参看http://drupal.org/node/1096746)。其次需要注意的是,module文件中不要使用php结束标签 ?>;这个结束标签对于PHP来说是可选的,但在Drupal中,有可能导致文件的尾部空格问题(参看http://drupal.org/node/545)。

 

接着,让我们看一下Drupal中注释的写法。我们首先从/**开始,在接下来的每一行中,缩进一格并以*开头,最后以*/结束。指令@file表示它下面的文本,是一个给出这个文件用途的描述。可以使用模块api.module,将这些注释提取成API文档。接着空了一行,后面跟着一段更长的描述,它用来向其它程序员说明这个模块是做什么的。

 

    下面我们要做的就是编写模块的逻辑了。我们在前面讲过,我们这个模块目的是为区块添加一个“更多链接”属性。为此,我们首先需要让用户为区块输入这个“更多链接”,在什么地方输入呢?应该在区块的配置页面,以及区块的添加页面,在这两个页面允许用户输入“更多链接”。首先让我们以区块的配置页面为例,我们先来看看这个页面。

 

1.png

                       图2-1默认的区块配置页面

 

    我们看到这个配置页面里面包含了一个表单,如果在这个页面,能够有那么一个表单元素,允许我们输入这个区块所指向的更多链接,这样就完美了。然而这个默认页面,并没有为我们提供这样的表单元素,这个时候,对于很多刚刚接触Drupal的其它程序员来说,首先想到应该就是修改这个页面对应程序的源代码,直接将我们想要的表单元素加进来。这个时候,聪明一点的程序员,就会顺藤摸瓜,根据路径信息,或者页面里面表单元素的信息,就会找到这个页面对应的代码,这就是位于block模块中block.admin.inc里面的block_admin_configure函数。

 

function block_admin_configure($form, &$form_state, $module, $delta) {

  $block = block_load($module, $delta);

  $form['module'] = array(

    '#type' => 'value',

    '#value' => $block->module,

  );

  $form['delta'] = array(

    '#type' => 'value',

    '#value' => $block->delta,

  );

 

  // Get the block subject for the page title.

  $info = module_invoke($block->module, 'block_info');

  if (isset($info[$block->delta])) {

    drupal_set_title(t("'%name' block", array('%name' => $info[$block->delta]['info'])), PASS_THROUGH);

  }

 

  $form['settings']['title'] = array(

    '#type' => 'textfield',

    '#title' => t('Block title'),

    '#maxlength' => 64,

    '#description' => $block->module == 'block' ? t('The title of the block as shown to the user.') : t('Override the default title for the block. Use <em>!placeholder</em> to display no title, or leave blank to use the default block title.', array('!placeholder' => '<none>')),

    '#default_value' => isset($block->title) ? $block->title : '',

    '#weight' => -18,

  );

 

  // Module-specific block configuration.

  if ($settings = module_invoke($block->module, 'block_configure', $block->delta)) {

    foreach ($settings as $k => $v) {

      $form['settings'][$k] = $v;

    }

  }

 

  // Region settings.

  $form['regions'] = array(

    '#type' => 'fieldset',

    '#title' => t('Region settings'),

    '#collapsible' => FALSE,

    '#description' => t('Specify in which themes and regions this block is displayed.'),

    '#tree' => TRUE,

  );

.

$form['actions'] = array('#type' => 'actions');

  $form['actions']['submit'] = array(

    '#type' => 'submit',

    '#value' => t('Save block'),

  );

 

  return $form;

}

 

    这是一个表单API函数,现在我们还没有必要完全掌握表单API,我们只需要了解这个函数是用来定义表单元素的,在这里它将表单定义成为一个大的数组$form,而这个表单中的每一个元素,同样是一个由键值构成的数组。比如$form['settings']['title'],其定义如下:

 

$form['settings']['title'] = array(

    '#type' => 'textfield',

    '#title' => t('Block title'),

    '#maxlength' => 64,

    '#description' => $block->module == 'block' ? t('The title of the block as shown to the user.') : t('Override the default title for the block. Use <em>!placeholder</em> to display no title, or leave blank to use the default block title.', array('!placeholder' => '<none>')),

    '#default_value' => isset($block->title) ? $block->title : '',

    '#weight' => -18,

  );

 

    这里面,左边的'#type''#title''#maxlength''#description''#default_value''#weight'是表单元素的键名,右边是对应的值。'#type'用来设定这个表单元素的类型,这里是'textfield''#title'对应于这个表单元素的label'#maxlength'用来限制这个表单元素的最大长度,'#description'是表单元素后面的描述;'#default_value'用来设定这个表单元素的默认值;'#weight'决定了这个表单元素在整个表单中的位置,默认为0,重量越小越靠前,这里的值为-18,表示这个元素应该靠前放置。上述代码,经过Drupal的表单系统,就会被转换为HTML对应的表单元素。

 

    我想对于很多刚刚接触Drupal的人来说,上来就学习表单API的相关知识,开始肯定吃不消。对于这个模块实例,我没有从实现hook_menuhook_permhook_help这样简单的钩子开始讲解,主要是为了给大家呈现一个解决问题完整过程,希望让读者在学习Drupal api的同时,能够掌握解决问题的方法,另外就是想突出一下表单APIDrupal模块开发中的重要性,只有掌握了表单API,才能算的上真正熟悉了Drupal模块开发。

 

    如果我们在$form['settings']['title']下面,添加一个新的表单元素,用来让用户输入“更多链接”,也同样能够解决问题,但是这会带来更多的问题,比如Drupal版本升级了,从Drupal7.0升级到了Drupal7.2,此时如果你修改了源代码,升级到Drupal7.2以后,原有的改动都被替换掉了,除非你再次修改同样的代码,或者使用打补丁的方式。但是这些方式都是不足取的,对于我们这些普通的Drupal开发者来说,永远不要修改Drupal核心代码,这在Drupal中是不允许的。

 

    那么有读者就会问了,不在这里修改源代码,那在什么地方添加我们的表单元素?难道我们能够不修改Drupal核心代码,就能将想要的表单元素添加到这个页面?答案当然是可以的。Drupal将表单抽象成为了数组,这为表单定义、呈现、处理带来了极大的灵活性。Drupal在呈现表单时,为我们提供了两个钩子,用来修改表单,一个是hook_form_alter,另一个是hook_form_FORM_ID_alter。讲到这里,聪明一点的读者就会想到,通过实现这两个钩子函数,我们就可以向区块配置页面添加我们的“更多链接”表单元素了。

 

    但是这里使用哪个钩子呢?是同时需要实现两个钩子函数,还是只需要实现其中的一个就可以了?实际上,对于上面的两个钩子,在很多时候两者之间是通用的,我们在hook_form_alter中,可以修改多个表单,而在hook_form_FORM_ID_alter中只能修改一个表单,而从性能方面来看,hook_form_FORM_ID_alter的效率要稍微高一点,但是这点性能提升,绝对不会对你站点的性能带来显著的影响。

 

    经过前面的准备工作,我们知道我们应该在module文件中实现hook_form_FORM_ID_alter这个钩子函数。对于这个钩子函数,我们首先需要知道,这里面大写的FORM_ID是需要被替换成为实际的表单ID的,什么是表单ID?它通常就是定义表单的函数的名字,由于我们在前面找到了区块配置表单的对应函数block_admin_configure,所以这里的FORM_ID就应该被替换为block_admin_configure

 

    让我们来定义我们的第一个钩子函数:

 

function block_morelink_form_block_admin_configure_alter(&$form, &$form_state){

  $default_morelink_url = '';

  $form['settings']['morelink_url'] = array(

    '#type' => 'textfield',

    '#title' => t('More Link url'),

    '#maxlength' => 255,

    '#description' => t('The More Link url of the block as shown to the user.') ,

    '#default_value' =>  $default_morelink_url,

    '#weight' => -17,

  );

}

 

    在这个钩子函数中,我们新增了一个表单元素$form['settings']['morelink_url'],我们可以通过这个表单元素来输入更多链接所指向的URL

 

    对于一个链接,它通常包含2部分,一部分是链接文本,这里面我们可以使用“更多”这一固定文本就可以了;另一部分是链接指向的路径,就是前面我们所定义的;此外,还有一个重要的组成部分,那就是鼠标移到链接上,所显示的提示文本,这是我们目前所忽略的,但对于实际的SEO非常有用。尽管后者可有可无,让我们还是在这个钩子函数中,为其新增一个表单元素:

 

function block_morelink_form_block_admin_configure_alter(&$form, &$form_state){

  $default_morelink_url = '';

  $default_morelink_title = '';

  $form['settings']['morelink_url'] = array(

    '#type' => 'textfield',

    '#title' => t('More Link url'),

    '#maxlength' => 255,

    '#description' => t('The More Link url of the block as shown to the user.') ,

    '#default_value' =>  $default_morelink_url,

    '#weight' => -17,

  );

  $form['settings']['morelink_title'] = array(

    '#type' => 'textfield',

    '#title' => t('More Link title'),

    '#maxlength' => 255,

    '#description' => t('The More Link title of the block as shown to the user.') ,

    '#default_value' =>  $default_morelink_title,

    '#weight' => -17,

  );

}

    我们在解决问题的时候,很多时候并不能一步到位,比如这里新增的这个表单元素,我们就是出于SEO的考虑,而新增过来的。新增的这个表单元素,并不会为后续开发带来很多麻烦。启用这个模块,现在我们在区块的配置页面,就能够看到我们新增的两个表单元素了。如图2-2所示。

 

    我们在前面还提到区块的添加页面,这里也是一个表单页面,我们也需要为其新增两个同样的表单元素。我们找到区块的添加页面对应的函数block_add_block_form,同样位于block模块的block.admin.inc文件里面,代码如下:

 

function block_add_block_form($form, &$form_state) {

  return block_admin_configure($form, $form_state, 'block', NULL);

}

 

    这个函数相当简单,它直接把表单的定义工作委托给了block_admin_configure。我们在这里知道了这个表单的IDblock_add_block_form,尽管实际工作都是由block_admin_configure完成的,但是在这里,表单ID变了。因此我们需要在我们的模块中,为block_add_block_form定义钩子函数,代码如下:

 

function block_morelink_form_block_add_block_form_alter(&$form, &$form_state) {

  block_morelink_form_block_admin_configure_alter($form, $form_state);

}

 

2.png 

                       图2-2 带有更多链接输入的区块配置页面

 

    

    我们也仿照着block_add_block_form,在我们的钩子函数block_morelink_form_block_add_block_form_alter中,我们将表单的修改工作,都委托给了block_morelink_form_block_admin_configure_alter,这就是我们前面定义好的钩子函数。我们再次打开区块的添加页面,就可以看到新增的两个表单元素了。

 

    接下来需要考虑的是,这个表单提交时,对于新增表单元素所提交的数据,我们如何处理?显然Drupal核心中的代码,并不知道我们新增了两个表单元素,所以核心部分是不会负责处理我们新增的这两个元素的,因此我们需要自己对这两个元素负责。Drupal允许我们在hook_form_FORM_ID_alter钩子函数中,追加新的表单验证函数和提交函数。我们分别追加一个验证函数和一个提交函数:

function block_morelink_form_block_admin_configure_alter(&$form, &$form_state){

  $default_morelink_url = '';

  $default_morelink_title = '';

  $form['settings']['morelink_url'] = array(

    '#type' => 'textfield',

    '#title' => t('More Link url'),

    '#maxlength' => 255,

    '#description' => t('The More Link url of the block as shown to the user.') ,

    '#default_value' =>  $default_morelink_url,

    '#weight' => -17,

  );

  $form['settings']['morelink_title'] = array(

    '#type' => 'textfield',

    '#title' => t('More Link title'),

    '#maxlength' => 255,

    '#description' => t('The More Link title of the block as shown to the user.') ,

    '#default_value' =>  $default_morelink_title,

    '#weight' => -17,

  );

  $form['#validate'][] = 'block_morelink_block_admin_configure_validate';

  $form['#submit'][] = 'block_morelink_block_admin_configure_submit';

}

 

    注意这里面$form['#validate']$form['#submit'],它们是两个数组,里面分别包含了这个表单的验证函数集,和提交函数集。我们新增的验证函数和提交函数,通常并不影响已有的验证函数和提交函数,表单在验证阶段会调用它上面所有的验证函数,而在提交阶段,则会调用它上面所有的提交函数。

 

    我们在module文件中,建立这两个函数:

 

/**

 * Form validate handler for block configuration form.

 */

function block_morelink_block_admin_configure_validate($form, &$form_state){

  //Todo

}

/**

 * Form submit handler for block configuration form.

 */

function block_morelink_block_admin_configure_submit($form, &$form_state){

  //Todo

}

 

    在验证函数中,我们可以验证URL的有效性,但是在实际的开发应用中,这些验证并不是很重要,用户只需要自己输入有效的URL就可以了。所以我们暂时先不考虑这个验证函数。而在提交函数中,我们则需要把用户输入的信息保存到数据库中,显然我们现在还没有准备好用来存储数据的数据库表。


Drupal版本: