第8章 Drupal区块

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

区块就是位于主内容区域外的一段小的文本,通常可以放在左边栏、右边栏、页首、页尾等这样的边边角角的位置。其实我们对区块是不陌生的,我们在第二章学习模块开发的时候,开发的模块就是用来扩展区块的属性的。只要我们访问过Drupal站点,其实就见识过区块。


Drupal版本:

1 什么是区块?

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

区块包含一个标题和一个描述,主要用于广告、html片段等辅助功能,它一般不用作主内容;因此,区块不是节点,它与节点有着不同的规则。区块也不是Drupal7中的实体。

 

区域是站点上用来放置区块的部分。区域的创建和显示是由主题(位于主题的.info文件中)负责的,而不是通过区块API来定义。如果一个区块,没有为其指定区域,那么它将无法显示出来。

 

    当然,这里说的是一般情况,在实际中,当启用了一些第三方模块以后,区块本身就是节点,区块本身也是一个实体。此外,我们还可以直接将区块输出在任意模板的任意位置上。这和一般情况大不相同,也就是有例外的时候。

 

   因此,有时候,我们真的很难界定什么才是区块。Drupal7中,一个小小的区块就演化出来了这么多可能。那么我们就直观的先看看Drupal内部自带的区块。

图片1.png 

    我们有三种方式来创建区块,一种方式是通过后台来添加区块,这种方式通常适用于添加静态的html区块;另一种常见的方式是使用views,对于Drupal中动态的各种区块,我们基本上都可以使用views列出,这应该是实际当中最常见的方式了,因为用的特别多,所以我们把它单列了出来;第三种方式,就是通过实现区块API,用模块代码的方式创建区块。

    对于动态的区块,如果是内容列表的形式,那么首先选择views;其次是选用模块代码的方式;当然,如果你想图省事,也可以通过后台创建,不过你需要启用PHP filter模块,并且在区块的正文中,添加对应的PHP代码,这是模块方式和后台方式的混合体。当然,在后台的配置界面使用PHP代码,有各种潜在的危险性,尽可能需要避免。

 

    如果我们开发一个实际的站点,需要定制一些杂七杂八的代码,比如包含多个动态的小区块,而这些区块也不方便使用views实现。此时我们可以为该站点开发一个专有的模块,把所有的这些没有特别关系的代码放在一起。比如,我为中华书局开发网上书店,我就专门创建了一个名为zhbc的模块,里面放置了很多特定于该站点的代码,其中就包含许多动态区块。


Drupal版本:

10 钩子hook_block_save

作者:老葛,北京亚艾元软件有限责任公司,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']);

  }

}

 


Drupal版本:

11 钩子hook_block_view

作者:老葛,北京亚艾元软件有限责任公司,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数据库进行查询,来获取里面的最新帖子,将帖子标题显示为链接形式,这样点击这个链接,就会自动地进入对应的论坛帖子页面了。将区块启用后,效果如图所示:

图片1.png 

图“论坛最新帖子”区块实际效果图

    

    在一个模块中,我们可以定义多个区块,只需要使用delta标识判断即可,我们在这里只用这么一个作为例子。


Drupal版本:

12 PHP代码的形式

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

    我们在前面提到过,不需要定义模块,只需要使用PHP filter模块,在自定义区块的内容里面,使用PHP代码,也可以实现动态的区块。我们以上面的例子作为对比,采用PHP代码的方式,实现同样的功能。

    为此我们需要在区块管理界面添加一个自定义区块,配置信息如图所示:

图片1.png 

                 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函数中的代码基本一致,只是稍微做了修改。保存这个区块后,显示的效果如图所示:

                     图片2.png

                           PHP code形式的区块

    这和我们在前面通过模块的形式,实现的功能是完全一样的,所不同的是,我们在这里没有实现任何钩子函数,省了很多功夫。这种方式对于我们这些开发者来说,可以省事不少。我记得以前,我还在公司的时候,做Drupal项目,我和同事合作,我看到他做了一个动态的区块,结果我在模块代码中怎么找都找不到,最后才发现,他放在了自定义区块中。

 

    使用这种方式,在省事的同时,也带来了多种潜在的问题。比如说,程序员的习惯不同,如果一个开发者采用了这种形式,开发了一个动态区块,后来他不再维护这个项目,换了另外的程序员,这种PHP code的形式,就不利于后者理解这段代码的逻辑。此外,对于站点的其它管理员来说,如果他不小心编辑了这个区块,假如在他看来没有做任何修改,只是网站启用了CKeditor标签,不经意之间就会带来PHP语法错误。PHP语法错误的后果,和HTML语法错误的后果完全两回事的。后者仅仅会给个警告,而前者则会使整个网站挂掉。

 

    推荐大家少采用这种方式,即便是采用了,也尽可能的在方便的时候,将其转换为模块的形式。其实模块的形式,熟悉了之后,也复杂不到哪里去。


Drupal版本:

13 扩展阅读

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

    为了帮助大家进一步的了解区块系统,建议大家有空的时候研究使用一下这么几个第三方模块。

 

    Bean

    模块地址:http://drupal.org/project/bean。bean是“block is entity not a node”的缩写。在这里区块是一个实体,而不是节点。

 

    Boxes

    模块地址:http://drupal.org/project/boxes。区块在这里不再是内容了,而仅仅是配置,因此可以使用features模块将其导入导出。

 

    Node Blocks

    模块地址:http://drupal.org/project/nodeblock。这个模块允许使用一个内容类型来处理区块。

 

    MultiBlock

    模块地址:http://drupal.org/project/multiblock。这个模块可以将一个区块同时显示在不同的区域中。

 

    CCK Blocks

    模块地址:http://drupal.org/project/cck_blocks。这个模块可以将一个实体的特定字段显示成为区块。这个模块刚好和bean模块反了过来。

 

    研究学习了这几个模块以后,相信你对区块系统的认识会更加模糊了,是的更加模糊了。什么是区块?区块是什么?不同的人会有不同的答案。对于这样的问题,我们没有必要深究,只需要能够灵活的驾驭这些模块,让其帮助我们解决实际问题就可以了。


Drupal版本:

14 总结

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

在本章中,你学到了以下几点:

区块是什么以及它们与节点的区别

区块的可见性和位置设置是如何工作的

如何定义一个区块

区块相关的扩展阅读


Drupal版本:

2 区块配置选项

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

 

    无论是自定义的区块,还是通过编写模块实现的区块,我们都可以在后台对其进行配置,导航到“首页 » 管理 » 结构 » 区块”,在这里找到“Powered by Drupal”区块,点击它的配置链接,我们就可以看到一个典型的区块配置页面,如图所示:

 

图片1.png

                区块配置选项截图

    在这里,可配置的有三项:区块标题、区域、可见性。自定义区块还包括区块描述、区块内容两部分。在区块标题中,可以在这里输入想要显示的标题,如果不想输出区块标题,可以输入<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代码,功能上是一致的。

 

基于用户的可见性设置:管理员可以允许个人用户在他们的帐户设置中,自己设定特定区块的可见性。用户可以在个人账户的编辑页面,来修改区块的可见性。

 

 


Drupal版本:

3 区块位置

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

 

我在前面提到,在Drupal的区块配置页面中,管理员可以选择将区块放置在哪个区域中。在区块的后台主页面,我们还可以对同一区域内的不同区块进行排序。区域是通过主题层的.info文件定义的,而不是通过区块API,而且不同的主题可以有不同的区域。关于创建区域的更多信息,可参看后续的主题系统一章。

 

                       区块的位置可以上下拖动

 

 

图片1.png

Drupal版本:

4 理解区块的呈现

作者:老葛,北京亚艾元软件有限责任公司,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_mainsystem_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一章。


Drupal版本:

5 区块的数据库表结构

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

在模块中,通过使用一组区块相关的钩子,便可以定义区块了,而且一个模块可以定义多个区块。一旦区块定义好了以后,它就会显示在区块管理界面。另外,管理员可以通过后台管理界面,手动的创建自定义区块;当然,也可以使用views模块创建各种列表区块。在本节中,我们主要讨论,如何通过自定义模块的方式来创建区块。首先让我们了解一下区块的数据库表结构,如图所示。

图片1.png 

                         区块的数据库表结构

 

每个区块的相关属性大都存储在表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字段的值为01,那么该字段将包含一列Drupal路径。如果visibility字段的值为2,那么该字段将包含一段PHP代码,通过对其计算来判定是否需要显示区块。

 

title:这是区块的自定义标题。如果这个字段为空,那么将使用区块的默认标题(由区块所在的模块提供)。如果这个字段为<none>,那么该区块就不显示标题。其余情况,该字段的文本将用作区块的标题。

 

cache: 这个值用来判定Drupal是如何缓存该区块的。-2表示自定义缓存,从区块缓存系统的角度来看,它等价于不缓存;当标准区块缓存无效时,比如使用了节点访问控制时,此时又需要缓存,那么就可以使用这种方式。–1表示区块不被缓存。1表示基于角色缓存区块,如果没有声明缓存设置,那么这是Drupal区块的默认设置。2表示基于用户缓存区块。4表示基于页面缓存区块。8表示区块将被全局缓存,也就是说,不管是什么角色、什么用户、什么页面,缓存的方式都是一样的。

 


Drupal版本:

6 区块钩子介绍

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

    如果使用模块的方式创建区块,那么我们就需要了解与区块相关的钩子。区块模块提供的钩子可以参看modules\block文件夹下面的block.api.php文件。主要包含以下钩子:

 

hook_block_info

    这个钩子用来声明该模块提供哪些区块,同时在这里还可以指定初始化的区块设置。在这个钩子中,可以定义多个区块,每个区块都有一个标识ID,这就是前面提到的deltaDelta主要用于:

    作为参数传递给其它区块钩子,比如hook_block_configurehook_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

   在区块信息保存到数据库之前,修改区块的定义信息。

 



Drupal版本:

7 创建一个区块

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

在这个例子中,我们将创建一个区块,用于获取Discuz论坛中的最新帖子。我们知道,Discuz是中国最流行的论坛软件,很多使用Drupal建站的用户,由于各种原因选择了Discuz作为网站的论坛。此时两者之间用户的集成,内容的集成成了首要解决得问题。我们这里讨论的就是内容的集成,在Drupal里面显示Discuz中的最新帖子。

 

让我们创建一个名为discuz_topics的模块,它将包含我们的区块代码。在路径sites/all/modules/custom下面创建一个名为discuz_topics的文件夹。然后创建文件discuz_topics.infodiscuz_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' => '',

);


Drupal版本:

8 钩子hook_block_info

作者:老葛,北京亚艾元软件有限责任公司,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;

}

图片1.png 

             图 新建区块“最新的Discuz帖子”显示在了区块列表页面。

 

    注意数组的info键只显示在管理界面,而不是显示给普通用户的区块标题。我们将在接下来的hook_block_view钩子中实现真正的区块标题。


Drupal版本:

9 钩子hook_block_configure

作者:老葛,北京亚艾元软件有限责任公司,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;

}

    我们添加了两个新的表单字段,当点击区块的配置链接时,就可以看到这两个字段了,如图所示。

 

 

图片1.png 

              图 我们定义的配置项显示在了区块配置表单中

 


Drupal版本: