作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
区块就是位于主内容区域外的一段小的文本,通常可以放在左边栏、右边栏、页首、页尾等这样的边边角角的位置。其实我们对区块是不陌生的,我们在第二章学习模块开发的时候,开发的模块就是用来扩展区块的属性的。只要我们访问过Drupal站点,其实就见识过区块。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
区块包含一个标题和一个描述,主要用于广告、html片段等辅助功能,它一般不用作主内容;因此,区块不是节点,它与节点有着不同的规则。区块也不是Drupal7中的实体。
区域是站点上用来放置区块的部分。区域的创建和显示是由主题(位于主题的.info文件中)负责的,而不是通过区块API来定义。如果一个区块,没有为其指定区域,那么它将无法显示出来。
当然,这里说的是一般情况,在实际中,当启用了一些第三方模块以后,区块本身就是节点,区块本身也是一个实体。此外,我们还可以直接将区块输出在任意模板的任意位置上。这和一般情况大不相同,也就是有例外的时候。
因此,有时候,我们真的很难界定什么才是区块。Drupal7中,一个小小的区块就演化出来了这么多可能。那么我们就直观的先看看Drupal内部自带的区块。
我们有三种方式来创建区块,一种方式是通过后台来添加区块,这种方式通常适用于添加静态的html区块;另一种常见的方式是使用views,对于Drupal中动态的各种区块,我们基本上都可以使用views列出,这应该是实际当中最常见的方式了,因为用的特别多,所以我们把它单列了出来;第三种方式,就是通过实现区块API,用模块代码的方式创建区块。
对于动态的区块,如果是内容列表的形式,那么首先选择views;其次是选用模块代码的方式;当然,如果你想图省事,也可以通过后台创建,不过你需要启用PHP filter模块,并且在区块的正文中,添加对应的PHP代码,这是模块方式和后台方式的混合体。当然,在后台的配置界面使用PHP代码,有各种潜在的危险性,尽可能需要避免。
如果我们开发一个实际的站点,需要定制一些杂七杂八的代码,比如包含多个动态的小区块,而这些区块也不方便使用views实现。此时我们可以为该站点开发一个专有的模块,把所有的这些没有特别关系的代码放在一起。比如,我为中华书局开发网上书店,我就专门创建了一个名为zhbc的模块,里面放置了很多特定于该站点的代码,其中就包含许多动态区块。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
当区块配置表单被提交后,区块系统并不会自动的保存我们新增的设置,此时我们还需要实现钩子hook_block_save,使用它来保存我们新增表单字段的值:
/**
* 实现钩子hook_block_save().
*/
function discuz_topics_block_save($delta = '', $edit = array()) {
// 当区块的delta就是我们前面定义的topics时
if ($delta == 'topics') {
variable_set('discuz_topics_num', $edit['discuz_topics_num']);
variable_set('discuz_topics_base_url', $edit['discuz_topics_base_url']);
}
}
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
通过使用Drupal函数variable_set(),我们将帖子数量和基路径的配置保存了下来。最后添加hook_block_view的实现,当区块显示时,返回Discuz最新帖子:
/**
* 实现钩子hook_block_view().
*/
function discuz_topics_block_view($delta = '') {
$block = array();
switch ($delta) {
case 'topics':
$block['subject'] = t('论坛最新帖子');
$block['content'] = discuz_topics_get_recent_topics();
break;
}
return $block;
}
在这里我们使用$block['subject']设置区块的默认标题,使用$block['content']设置区块的内容,我们将区块内容的显示委托给了discuz_topics_get_recent_topics函数,这样使得hook_block_view钩子中的代码逻辑更加清晰一点。现在让我们看看这个函数:
/**
* 区块内容的回调函数.
*/
function discuz_topics_get_recent_topics(){
$output = "";
$num = variable_get('discuz_topics_num', 5);
$base_url=variable_get('discuz_topics_base_url','http://localhost/discuz');
$query = Database::getConnection('default', 'discuz')
->select('posts', 'p')
->fields('p', array('tid', 'subject'))
->condition('p.first', 1, '=')
->range(0, $num)
->orderBy('p.pid','DESC');
$result = $query->execute();
$output .= "<ul class='discuz-topics'>";
foreach ($result as $record) {
$output .= '<li>';
$output .= l($record->subject,$base_url.'/viewthread.php?tid='.$record->tid);
$output .= '</li>';
//drupal_set_message($record->subject);
}
$output .= "</ul>";
return $output;
}
这里,我们通过对Discuz数据库进行查询,来获取里面的最新帖子,将帖子标题显示为链接形式,这样点击这个链接,就会自动地进入对应的论坛帖子页面了。将区块启用后,效果如图所示:
图“论坛最新帖子”区块实际效果图
在一个模块中,我们可以定义多个区块,只需要使用delta标识判断即可,我们在这里只用这么一个作为例子。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
我们在前面提到过,不需要定义模块,只需要使用PHP filter模块,在自定义区块的内容里面,使用PHP代码,也可以实现动态的区块。我们以上面的例子作为对比,采用PHP代码的方式,实现同样的功能。
为此我们需要在区块管理界面添加一个自定义区块,配置信息如图所示:
PHP code形式的实现
在区块正文的文本格式中,我们选择了“PHP code”,同时在正文中输入了以下内容:
<?php
$output = "";
$num = 5;
$base_url = 'http://localhost/discuz';
$query = Database::getConnection('default', 'discuz')
->select('posts', 'p')
->fields('p', array('tid', 'subject'))
->condition('p.first', 1, '=')
->range(0, $num)
->orderBy('p.pid','DESC');
$result = $query->execute();
$output .= "<ul class='discuz-topics'>";
foreach ($result as $record) {
$output .= '<li>';
$output.=l($record->subject,$base_url.'/viewthread.php?tid='.$record->tid);
$output .= '</li>';
}
$output .= "</ul>";
print $output;
?>
这段代码和前面的discuz_topics_get_recent_topics函数中的代码基本一致,只是稍微做了修改。保存这个区块后,显示的效果如图所示:
PHP code形式的区块
这和我们在前面通过模块的形式,实现的功能是完全一样的,所不同的是,我们在这里没有实现任何钩子函数,省了很多功夫。这种方式对于我们这些开发者来说,可以省事不少。我记得以前,我还在公司的时候,做Drupal项目,我和同事合作,我看到他做了一个动态的区块,结果我在模块代码中怎么找都找不到,最后才发现,他放在了自定义区块中。
使用这种方式,在省事的同时,也带来了多种潜在的问题。比如说,程序员的习惯不同,如果一个开发者采用了这种形式,开发了一个动态区块,后来他不再维护这个项目,换了另外的程序员,这种PHP code的形式,就不利于后者理解这段代码的逻辑。此外,对于站点的其它管理员来说,如果他不小心编辑了这个区块,假如在他看来没有做任何修改,只是网站启用了CKeditor标签,不经意之间就会带来PHP语法错误。PHP语法错误的后果,和HTML语法错误的后果完全两回事的。后者仅仅会给个警告,而前者则会使整个网站挂掉。
推荐大家少采用这种方式,即便是采用了,也尽可能的在方便的时候,将其转换为模块的形式。其实模块的形式,熟悉了之后,也复杂不到哪里去。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
为了帮助大家进一步的了解区块系统,建议大家有空的时候研究使用一下这么几个第三方模块。
模块地址:http://drupal.org/project/bean。bean是“block is entity not a node”的缩写。在这里区块是一个实体,而不是节点。
模块地址:http://drupal.org/project/boxes。区块在这里不再是内容了,而仅仅是配置,因此可以使用features模块将其导入导出。
模块地址:http://drupal.org/project/nodeblock。这个模块允许使用一个内容类型来处理区块。
模块地址:http://drupal.org/project/multiblock。这个模块可以将一个区块同时显示在不同的区域中。
模块地址:http://drupal.org/project/cck_blocks。这个模块可以将一个实体的特定字段显示成为区块。这个模块刚好和bean模块反了过来。
研究学习了这几个模块以后,相信你对区块系统的认识会更加模糊了,是的更加模糊了。什么是区块?区块是什么?不同的人会有不同的答案。对于这样的问题,我们没有必要深究,只需要能够灵活的驾驭这些模块,让其帮助我们解决实际问题就可以了。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
在本章中,你学到了以下几点:
区块是什么以及它们与节点的区别
区块的可见性和位置设置是如何工作的
如何定义一个区块
区块相关的扩展阅读
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
无论是自定义的区块,还是通过编写模块实现的区块,我们都可以在后台对其进行配置,导航到“首页 » 管理 » 结构 » 区块”,在这里找到“Powered by Drupal”区块,点击它的配置链接,我们就可以看到一个典型的区块配置页面,如图所示:
区块配置选项截图
在这里,可配置的有三项:区块标题、区域、可见性。自定义区块还包括区块描述、区块内容两部分。在区块标题中,可以在这里输入想要显示的标题,如果不想输出区块标题,可以输入<none>,留空则表示使用默认标题。区域设置里面可以配置这个区块在主题中所属的区域,如果留空则表示在该主题下面不显示。可见性设置,又分为基于页面路径的可见性设置、基于用户角色的可见性设置、基于内容类型的可见性设置、基于用户的可见性设置。
基于页面路径的可见性设置:在这里可以配置区块显示在哪些页面,或者不显示在哪些页面。此处可以使用通配符,支持路径别名。结合Pathauto模块和这里的选项,可以方便的控制区块的显示。
假如我们创建一个book节点类型,为所有该类型的节点设置别名为:book/item/nid的形式,book相关的列表页面使用books/tid的形式。那么假如我们想将一个区块只显示在book的节点页面和列表页面,那么我们可以选择“只在下列页面”,然后在下面的文本域中输入:
book/item/*
books*
如果启用PHP filter模块,还可以使用PHP代码来控制区块的可见性。在基于页面路径的可见性设置中,选择PHP代码方式。然后在下面的文本域中输入PHP代码。当显示一个页面时,Drupal将运行这里的php代码片段,来判定该区块是否显示。每段代码都应该返回TRUE或FALSE,来指示区块对于特定请求是否可见。
比如将区块显示给登录用户的PHP代码:
<?php
global $user;
return (bool) $user->uid;
?>
基于内容类型的可见性设置:这里可以选择区块显示在哪些内容类型的节点页面下。还拿前面的例子来说,我们想在book类型的节点页面显示该区块,那么只需要在这里选择book内容类型即可。
特定角色可见性设置:管理员可以选择,区块对哪些特定角色的用户可见。如果想将区块显示给登录用户,只需要在这里选择“注册用户”即可。这和前面所给PHP代码,功能上是一致的。
基于用户的可见性设置:管理员可以允许个人用户在他们的帐户设置中,自己设定特定区块的可见性。用户可以在个人账户的编辑页面,来修改区块的可见性。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
我在前面提到,在Drupal的区块配置页面中,管理员可以选择将区块放置在哪个区域中。在区块的后台主页面,我们还可以对同一区域内的不同区块进行排序。区域是通过主题层的.info文件定义的,而不是通过区块API,而且不同的主题可以有不同的区域。关于创建区域的更多信息,可参看后续的主题系统一章。
区块的位置可以上下拖动
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
在一个页面请求周期内,在构建页面呈现数组时,Drupal允许模块通过实现钩子函数hook_page_build,向页面数组追加其它元素。区块系统通过实现这个钩子函数,把所有区域作为页面元素添加了进来。
function block_page_build(&$page) {
global $theme;
// The theme system might not yet be initialized. We need $theme.
drupal_theme_initialize();
// Fetch a list of regions for the current theme.
$all_regions = system_region_list($theme);
$item = menu_get_item();
if ($item['path'] != 'admin/structure/block/demo/' . $theme) {
// Load all region content assigned via blocks.
foreach (array_keys($all_regions) as $region) {
// Assign blocks to region.
if ($blocks = block_get_blocks_by_region($region)) {
$page[$region] = $blocks;
}
}
...
}
...
}
在每个区域对应的呈现数组中,包含了里面所有启用的区块。
function block_get_blocks_by_region($region) {
$build = array();
if ($list = block_list($region)) {
$build = _block_get_renderable_array($list);
}
return $build;
}
区域内所有区块共同组成了区域呈现数组:
function _block_get_renderable_array($list = array()) {
$weight = 0;
$build = array();
foreach ($list as $key => $block) {
$build[$key] = $block->content;
unset($block->content);
//...
//添加区块的上下文链接,system_main、system_help除外
if ($key != 'system_main' && $key != 'system_help') {
$build[$key]['#contextual_links']['block'] = array('admin/structure/block/manage', array($block->module, $block->delta));
}
$build[$key] += array(
'#block' => $block,
'#weight' => ++$weight,
);
$build[$key]['#theme_wrappers'][] = 'block';
}
$build['#sorted'] = TRUE;
return $build;
}
还记得我们在前面表单系统一章中,讲到的呈现数组吧,是的,区块区域的显示,也采用了呈现数组。在构建呈现数组中,我们对主题内的所有可用区域进行迭代,然后又对区域内所有区块进行迭代。有关呈现数组的相关知识,可参看表单API一章。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
在模块中,通过使用一组区块相关的钩子,便可以定义区块了,而且一个模块可以定义多个区块。一旦区块定义好了以后,它就会显示在区块管理界面。另外,管理员可以通过后台管理界面,手动的创建自定义区块;当然,也可以使用views模块创建各种列表区块。在本节中,我们主要讨论,如何通过自定义模块的方式来创建区块。首先让我们了解一下区块的数据库表结构,如图所示。
区块的数据库表结构
每个区块的相关属性大都存储在表block里面,自定义区块的描述和内容信息存放在block_custom表中,与内容类型相关的可见性设置存放在block_node_type里面,而与角色相关的可见性设置则存放在block_role里面。我们先学习一下表block中的各种属性:
bid:这是每个区块的唯一标识ID。
module:这个字段存放的是定义区块的模块的名称。比如,用户登录区块是在用户模块中定义的,它在这里的module值就是user。对于管理员在“首页 » 管理 » 结构 » 区块”中创建的自定义区块,则被认为是由区块模块创建的,此时的module值就是block。
delta:由于一个模块可以在钩子hook_block_info中定义多个区块,所以delta存放了每个区块的键,它们在该模块的hook_block_info钩子实现中是唯一的,但对于整个站点的所有区块,则不一定是唯一的。delta可以是整数,也可以是字符串。
theme:一个区块可以用于多个主题。因此,Drupal需要存放启用了该区块的主题的名称。对于启用了该区块的每个主题,在这个数据库表中都有自己的对应记录。配置选项不能在主题之间共享。
status:它用来追踪区块是否被启用。1意味着启用,0意味着禁用。如果一个区块,没有为其指定一个区域,那么Drupal会将其状态设置为0.
weight:区块的重量,用来判定区块在区域中的相对位置。
region: 放置该区块的区域的名字,例如,footer(页脚)、header(页首)。
custom: 这是这个区块的“基于用户的可见性设置”里面的值。0意味着用户不能控制该区块的可见性;1意味着该区块默认是显示的,但是用户可以隐藏它;2意味着该区块默认是隐藏的,但是用户可以选择显示它。
visibility: 这个值属于“基于页面路径的可见性设置”,用来表示如何判定区块的可见性。0意味着区块将显示在除所列页面以外的所有页面;1意味着区块只能显示在下面的所列页面;2意味着,Drupal将通过执行一段由管理员定义的PHP代码,来判定区块的可见性。
pages: 该字段的内容依赖于visibility字段中的设置。如果visibility字段的值为0或1,那么该字段将包含一列Drupal路径。如果visibility字段的值为2,那么该字段将包含一段PHP代码,通过对其计算来判定是否需要显示区块。
title:这是区块的自定义标题。如果这个字段为空,那么将使用区块的默认标题(由区块所在的模块提供)。如果这个字段为<none>,那么该区块就不显示标题。其余情况,该字段的文本将用作区块的标题。
cache: 这个值用来判定Drupal是如何缓存该区块的。-2表示自定义缓存,从区块缓存系统的角度来看,它等价于不缓存;当标准区块缓存无效时,比如使用了节点访问控制时,此时又需要缓存,那么就可以使用这种方式。–1表示区块不被缓存。1表示基于角色缓存区块,如果没有声明缓存设置,那么这是Drupal区块的默认设置。2表示基于用户缓存区块。4表示基于页面缓存区块。8表示区块将被全局缓存,也就是说,不管是什么角色、什么用户、什么页面,缓存的方式都是一样的。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
如果使用模块的方式创建区块,那么我们就需要了解与区块相关的钩子。区块模块提供的钩子可以参看modules\block文件夹下面的block.api.php文件。主要包含以下钩子:
hook_block_info:
这个钩子用来声明该模块提供哪些区块,同时在这里还可以指定初始化的区块设置。在这个钩子中,可以定义多个区块,每个区块都有一个标识ID,这就是前面提到的delta。Delta主要用于:
作为参数传递给其它区块钩子,比如hook_block_configure、hook_block_view。
在构建区块的html时,用来生成区块的html ID。
用于区块的模板建议block__MODULE__DELTA。
在hook_block_info_alter钩子中,供第三方模块使用。
在每个区块对应的关联数组中,可以包含以下键:
info:这个值是必须的。一个可翻译的字符串(使用t()封装),为站点管理员提供了区块的可读名字,非站点管理员看不到这一信息。
cache: 表示这个区块如何被缓存。可能的值有DRUPAL_NO_CACHE (不缓存区块)、 DRUPAL_CACHE_CUSTOM(使用自定义的缓存)、DRUPAL_CACHE_PER_ROLE (基于角色缓存区块)、DRUPAL_CACHE_PER_USER (基于用户缓存区块,站点用户多时最好不要用这种方式!)、 DRUPAL_CACHE_PER_PAGE (基于页面缓存区块)、DRUPAL_CACHE_GLOBAL (缓存一次区块,所有的都一样)。
properties:添加到区块上的附加元数据数组。常用属性:'administrative'。
status:区块默认是否被启用。
region:为区块设置的默认区域。
weight:它控制着区块在区域内的相对位置。
visibility:基于页面路径的可见性设置。参看上一节的描述。
pages:取决于visibility。参看上一节。
hook_block_configure:
为区块定义一个配置表单。
hook_block_save:
保存来自于hook_block_configure的配置选项。
hook_block_view:
返回区块的呈现数组。
hook_block_info_alter:
在区块信息保存到数据库之前,修改区块的定义信息。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
在这个例子中,我们将创建一个区块,用于获取Discuz论坛中的最新帖子。我们知道,Discuz是中国最流行的论坛软件,很多使用Drupal建站的用户,由于各种原因选择了Discuz作为网站的论坛。此时两者之间用户的集成,内容的集成成了首要解决得问题。我们这里讨论的就是内容的集成,在Drupal里面显示Discuz中的最新帖子。
让我们创建一个名为discuz_topics的模块,它将包含我们的区块代码。在路径sites/all/modules/custom下面创建一个名为discuz_topics的文件夹。然后创建文件discuz_topics.info、discuz_topics.module。
接着,向discuz_topics.info文件中添加以下信息:
name = Discuz论坛帖子
description = 在Drupal中获取Discuz论坛的最新帖子
core = 7.x
接下来,向discuz_topics.module文件中添加以下代码:
<?php
/**
* @file
* 在Drupal中获取Discuz论坛的最新帖子,以区块的形式显示
*/
在添加好这些内容后,就可以在“首页 » 管理 » 模块”下面启用该模块。现在让我们再做一些准备工作,假定我们已经在本地安装好了Discuz论坛,里面已经添加了一些测试内容。我们需要在settings.php文件中添加以下信息,这里假定论坛的数据库名为discuz,数据库用户名为root,密码为空,用于连接Discuz的数据库:
$databases['discuz']['default'] = array(
'driver' => 'mysql',
'database' => 'discuz',
'username' => 'root',
'password' => '',
'host' => 'localhost',
'prefix' => '',
);
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
让我们添加钩子hook_block_info,这样,我们的区块就会显示在区块管理界面上了。
/**
* 实现钩子hook_block_info().
*/
function discuz_topics_block_info(){
$blocks['topics'] = array(
'info' => t('最新的Discuz帖子'),
// 默认使用DRUPAL_CACHE_PER_ROLE.
);
return $blocks;
}
图 新建区块“最新的Discuz帖子”显示在了区块列表页面。
注意数组的info键只显示在管理界面,而不是显示给普通用户的区块标题。我们将在接下来的hook_block_view钩子中实现真正的区块标题。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
接下来,我们需要为区块创建相关的配置选项,这个配置选项不是必须的,根据需要选用。我们打算帖子数量是可以配置的,同时对于帖子链接到的论坛网址的基路径,我们也希望能够配置,为此,需要实现hook_block_configure钩子,向module文件中添加以下代码:
/**
* 实现钩子hook_block_configure().
*/
function discuz_topics_block_configure($delta = '') {
$form = array();
//当区块的delta就是我们前面定义的topics时
if ($delta == 'topics') {
//显示在区块配置页面的表单元素
$form['discuz_topics_num'] = array(
'#type' => 'select',
'#title' => t('要显示的最新帖子的条目数量'),
'#default_value' => variable_get('discuz_topics_num', 5),
'#options' => drupal_map_assoc(array(5, 10, 15, 20, 25 , 30)),
);
$form['discuz_topics_base_url'] = array(
'#type' => 'textfield',
'#title' => t('论坛的基路径'),
'#default_value' => variable_get('discuz_topics_base_url', 'http://localhost/discuz'),
);
}
return $form;
}
我们添加了两个新的表单字段,当点击区块的配置链接时,就可以看到这两个字段了,如图所示。
图 我们定义的配置项显示在了区块配置表单中