你在这里

Chapter 10: Working With Blocks

区块是一片文本或功能区域,能放置在主题定义的区域中。模块可以是下面任何的东西:一个节点、一个节点列表、一个视频、一个表单、一个在线投票、一个对话窗口、一个fb的状态更新、或你任何能想象出来的东西。当我同客户谈及区块,他们经常这么回答“哦,你的意思区块就是一个控件”,通常这个术语用于Drupal范围之外那些表现为区块的东西,我将展示如何建立和使用区块。

区块是什么

一个区块本质上是一个独立的容器,它可以容纳你能想象出来任何虚拟东西。通过对几个例子的考察可能更容易理解什么是区块(表10-1),下表程序Drupal带的一些标准区块。

------------------------------------------------------------------------------------------------
Block                                  Description
------------------------------------------------------------------------------------------------
Login Form                             允许用户登录、注册、重置密码的表单
Who's online                           在此列出所有在站点登录的用户
Who's new                              显示站点最新用户列表的区块
Search form                            搜索表单时包含在block里面的
recent comments
Main menu and secondary menu
Recent content
Most recent poll
Active forum topics
------------------------------------------------------------------------------------------------
几个贡献模块,包括block就像一个他们传递的功能解决方案的组成,Ubercart提供了几个区块,用于显示访问者的购物车状态信息。
使用Block API和区块管理接口,你可以为你虚拟的有目的的的东西建立自定义区块,例如我将在下面建立的一些区块:
------------------------------------------------------------------------------------------------
Block                                  Description
------------------------------------------------------------------------------------------------
Recent Bloggers                        显示最后发贴的作者头像相册的区块
slideshow of upcoming events           显示未来节点预告的区块,像一个幻灯片
A chat form
A Donate now feature
A list of new books added to a
library's collection
A contact us form
A list of postings on multiple
social networking sites
A Google map showing recent
postings
------------------------------------------------------------------------------------------------

区块可以通过Drupal的管理界面或Block API(blocks模块提供)程序的随便一种方式定义,在创建一个区块时,你怎样知道是用的哪种方法?一个一次性区块如与站点有关的一小片静态HTML是自定义区块的一个好的候选者。那些关联到你的模块的天然是动态的区块,或者由大量PHP代码组成的是使用Block API的极好的候选者并且在模块内实现。试图在自定义区块中避免PHP代码,正如数据库中的代码维护要难于模块中的代码。站点的编辑会进步并意外地删除所有那些困难工作太容易了。如果在模块层建立一个区块没有什么意义,只要调用一个这个区块内的自定义函数并存储PHP代码到别处。


------------------------------------------------------------------------------------------------
Tip:一个通常的有关区块和其它独特成分的实践是去建立一个站点特有的模块并
将站点特定的功能放置到这个模块中,例如一个站点的开发者Jones Pies和
Soda Company可以建立一个jonespiessoda模块【特有功能分割原理】
------------------------------------------------------------------------------------------------
尽管block API相对简单,但是不要忽视框架内你做东西得复杂性。区块能显示你想出来任何东西(它们是用PHP写的,因此你能做的是无限的)但是它们通常履行对站点主要内用支持规则。例如,你能为每个用户规则建立一个自定义导航区块或者你能依赖于批准去暴露出一个评论列表。

区块配置选项

一个通用的配置选项你应该熟悉,设置一个区块的可视化配置页。区块的可视化定义当一个区块在一个页面上是否可见是基于你使用图10-2接口所指定的条件,用用户登录区块作为例子,你通过下面选项控制区块是否可见:
      + page_specific visibility settings:管理员可以选择在特定页面或一定范围的页面上区块是显示还是隐藏,或者
        你自定义PHP代码决定条件的真假
      + content types visibility settings:管理员能选择区块只能显示在一个特定内容的页面,如,只在论坛主题页显示
      + Role-specific visibility settings:管理员可以选择只给特定规则的用户显示区块
      + User-specific visibility settings:管理员能允许单独的用户去自定义给定区块的可视性,用户点击”My account”
        来修改区块的可视性

      定义一个区块

在模块中区块是用hook_block_info()定义的,并且一个模块可以用一个该函数定义多个区块,一旦区块定义了,它就将显示在区块管理页面。此外,站点管理员能通过web接口手工建立自定义模块。在本章,我们的焦点在程序化建立模块,让我们看看区块的数据库定义,如图10-3:

每个区块的所有属性都存储在block表中。那些由配置接口建立的区块的附加数据则存储在其它的提供的表中,如图10-3所示。
下面的属性定义在block表的列中:
      + bid:每个区块的唯一ID
      + module:这个列包含定义此区块的模块的名字,用户登录区块由user模块创建,等等。在后台手工建的区块呗认为是block模块建立的。
      + delta:因为模块在hook_block_info()中能定义多个区块,这个delta列里存储的是在同一个hook_block_info()中定义的区块的唯一键,delta应该是一个字符串。
      + theme:区块可以为多个主题定义,因此Drupal需要存储激活此区块的主题的名字,每一个激活此区块的主题在数据库中拥有自己的行,配置选项不跨主题共享。
      + status:跟踪区块是否被激活,1为激活,0为未激活,如果一个区块没有分配到一个区域,Drupal将status置为0.
      + weight:区块的宽度决定在同一区域内与其它区块的相关位置。
      + region:封装这个区块的区域名称如footer等。
      + custom:用户指定的可视性设置,为0意味着用户不能控制区块的可视性,为1意味着区块默认显示,用户不可以隐藏它,为2意味着区块默认隐藏,但用户能选择显示它。
      + visibility:确定区块怎样呈现,0表示在所有页面展示,除了列出的页面,1意味着只在列出的页面展示,2表示Drupal将执行自定义的PHP代码决定可视性。
      + pages:本字段的内容依赖于visibility字段的设置,visibility字段为0或1,则此字段包含页面的列表,visibility字段为2则此字段包含自定义的PHP代码来在显示此区块时执行。
      + title:区块的自定义标题。为空则区块使用默认标题(有创建区块的模块提供)如果为<none>,这不显示任何标题。
      + cache:确定Drupal怎样缓存此区块,-1不缓存,1为每个规则缓存,这是默认设置,2则为每个用户缓存,4为每个页面缓存,8则缓存,不管是规则、用户、页面都使用相同方式。

      使用区块钩子

区块钩子——hook_block_info(),hook_block_configure(),hook_block_save()和hook_block_view()处理所有程序化创建区块的逻辑,使用这些钩子,你能声明一个或多个区块,任何模块都可以通过实现这些钩子来建立区块,让我们看看这些钩子:
      + hook_block_info():定义所有模块提供的区块。
      + hook_block_configure($delta = ''):区块的配置表单,$delta参数是区块返回的ID,你能用为这个$delta参数使用一个整数或字符串,相同的参数也应用在hook_block_save和hook_block_view钩子中。
      + hook_block_save($delta = '', $edit = array()):为区块保存配置选项,$edit参数包含从区块配置表单提交的表单数据。
      + hook_block_view($delta = ''):处理区块在一个区域内激活来显示它的内容。

创建一个区块
      
 

在本例中,你建立两个区块,内容适度简单便于管理,首先,建立一个区块,列出待审批的评论,然后建立一个区块列出未发布的节点。两个区块都有连接到适度的配置表单。
让我们建立一个新的appval.module模块来承载我们的区块代码。
approval.info

name = Approval
description = Blocks for facilitating pending content workflow.
package = Pro Drupal Development
core = 7.x
version = VERSION
files[] = approval.module

approval.module

<?php

/**
 * @file
 * Implements various blocks to improve pending content workflow.
 */
一旦这个文件建立了,通过Modules页面激活这个模块,你能继续在approval.module文件内工作,继续编辑你的文本。
让我们增加hook_block_info(),那样我们的区块就呈现在区块管理页面的区块列表中了(见图10-4)。我将在info属性定义它显示的标题,status定义为True那样它就自动激活,region设置为sidebar_first,weight设置为0,visibility设置为1(可见)。

/**
 * Implements hook_block_info().
 */
function approval_block_info() {
  $blocks['pending_comments'] = array(
    'info' => t('Pending Comments'),
    'status' => TRUE,
    'region' => 'sidebar_first',
    'weight' => 0,
    'visibility' => 1,
  );
  return $blocks;
}

注意info的值不是区块展示给用户的标题,info是描述区块可以在区块列表里呈现,能由管理员配置,你能在以后通过hook_block_view()实现实际的区块标题。然而你首先要设置附加的配置选项,为此,需实现hook_block_configure钩子,你建立一个新的表单字段,它在点击configure连接后可以显示,如图10-5:

/**
 * Implements hook_block_configure().
 */
function approval_block_configure($delta) {
  $form = array();
  switch($delta) {
    case 'pending_comments' :
      $form['pending_comment_count'] = array(
        '#type' => 'textfield',
        '#title' => t('Configure Number of Comments to Display.'),
        '#size' => 6,
        '#description' => t('Enter the number of pending comments that will paaear in the block.'),
        '#default_value' => variable_get('pending_comment_count', 5),
      );
      break;
  }
  return $form;
}


当图10-5这个区块的配置表单提交时,将触发hook_block_save(),你将下面的程序保存这个表单字段:

/**
 * Implements hook_block_save().
 */
function approval_block_save($delta = '', $edit = array()) {
  switch($delta) {
    case 'pending_comments' :
      variable_set('pending_comment_count', (int)$edit['pending_comment_count']);
      break;
  }
  return;
}

你使用Drupal内置的变量系统variable_set()来保存未审评论数量,注意我们将类型转换为整型,最后,使用hook_block_view()和一个区块显示时返回一个未决评论列表的自定义函数来增加视觉操作:

/**
 * Implements hook_block_view().
 */
function approval_block_view(delta = '') {
  switch($delta) {
    case 'pending_comments' :
      $block['subject'] = t('Pending Comments');
      $block['content'] = approval_block_content($delta);
      return $block;
      break;
  }
}

/**
 * A module-defined blcok content function.
 */
function approval_block_contents($delta) {
  switch ($delta) {
    case 'pending_comments' :
      if (user_access('administer comments')) {
        $nbr_comments = variable_get('pending_comment_count');
        $result = db_query('SELECT cid, subject FROM {comment} WHERE status = 0 limit $nbr_comments");
        $items = array();
        foreach ($result as $row) {
          $items[] = l($row->subject, 'comment/' . $row->cid . '/edit');
        }
        return array('#markup' => theme('item_list', array('item' => $items)));
      }
      break;
  }
}

这里我们查询数据库来获得需要审批的评论,将这些评论的标题显示为连接,如图10-6
你还设置用下面的行设置了区块的标题:
$blockp['subject'] = t('Pending comments');


现在,Pending comments区块完成了,让我们定义另一个区块,这里含有approval_block_info()函数——它列出所有为发布节点并提供一个连接到它的编辑页:

/**
 * Implements hook_block_info().
 */
function approval_block_info() {
  $blocks['pending_comments'] = array(
    'info' => t('Pending Comments'),
    'status' => TRUE,
    'region' => 'sidebar_first',
    'weight' => 0,
    'visibility' => 1,
  );
  $block['unpublished_nodes'] = array(
    'info' => t('Unpublished nodes');
    'status' => TRUE,
    'region' => 'sidebar_first',
    'weight' => 0,
    'visibility' => 1,
  );
  return $block;
}

注意,每个区块都分配一个键($blcok['pending_comments'],$block['unpublished_nodes']...... $block['xxxxx'])。block模块随后将这些键作为$delta参数使用。
我将更新hook_block_configure()和hook_block_save()函数,增加设置显示的节点数量的表单保存由管理员在这个表单输入的值。

/**
 * Implements hook_block_configure().
 */
function approval_block_configure($delta) {
  $form = array();
  switch($delta) {
    case 'pending_comments' :
      $form['pending_comment_count'] = array(
        '#type' => 'textfield',
        '#title' => t('Configure Number of Comments to Display.'),
        '#size' => 6,
        '#description' => t('Enter the number of pending comments that will paaear in the block.'),
        '#default_value' => variable_get('pending_comment_count', 5),
      );
      break;
    case 'unpublished_nodes' :
      $form['unpublished_node_count'] = array(
        '#type' => 'textfield',
        '#title' => t('Configure Number of Nodes to Display'),
        '#size' => 6,
        '#description' => t('Enter the number of number of unpublished nodes that will appear in the block.');
        '#default_value' => variable_get('unpublished_node_count', 5),
      );
      break;
  }
  return $form;
}

/**
 * Implements hook_block_save().
 */
function approval_block_save($delta = '', $edit = array()) {
  switch($delta) {
    case 'pending_comments' :
      variable_set('pending_comment_count', (int)$edit['pending_comment_count']);
      break;
    case 'unpublished_nodes' :
      variable_set('unpublished_node_count', (int)$edit['unpublished_node_count']);
      break;
  }
  return;
}

我还将更新hook_block_view和approval_block_content函数来增加未发布节点的显示。
/**
 * Implements hook_block_view().
 */
function approval_block_view(delta = '') {
  switch($delta) {
    case 'pending_comments' :
      $block['subject'] = t('Pending Comments');
      $block['content'] = approval_block_content($delta);
      return $block;
      break;
    case 'unpublished_nodes' :
      $block['subject'] = t('Unpublished Nodes');
      $block['content'] = approval_block_content($delta);
      return $block;
      break;
  }
}

/**
 * A module-defined blcok content function.
 */
function approval_block_contents($delta) {
  switch ($delta) {
    case 'pending_comments' :
      if (user_access('administer comments')) {
        $nbr_comments = variable_get('pending_comment_count');
        $result = db_query('SELECT cid, subject FROM {comment} WHERE status = 0 limit $nbr_comments");
        $items = array();
        foreach ($result as $row) {
          $items[] = l($row->subject, 'comment/' . $row->cid . '/edit');
        }
        return array('#markup' => theme('item_list', array('item' => $items)));
      }
      break;
    case 'unpublished_nodes' :
      if (user_access('adminsier nodes')) {
        $nbr_nodes = variable_get('unpublished_node_count', 5);
        $result = db_query_range('SELECT nid, titlem FROM {node} WHERE status = 0', 0, $nbr_nodes);
        $items = array();
        foreach ($result as $row) {
          $items[] = l($row->title, 'node/' . $row->nid . '/edit');
        }
        return array('#markup' => theme('item_list', array('items' => $items)));
      }
      break;
  }
}

你的新未发布节点区块看起来像图10-7


在模块安装时激活模块

在approval.module,我们自动激活区块并分配他们到一个区域。例如我们建立了Pending Comments区块自动激活它(status = TRUE)并分配它到sidebar_first区域。
$blocks['pending_comments'] = array(
    'info' => t('Pending Comments'),
    'status' => TRUE,
    'region' => 'sidebar_first',
    'weight' => 0,
    'visibility' => 1,
  );
相同的情况,你可以希望让管理员来决定是否激活区块并决定将它分配给某个区域,为此,将status属性设置为FALSE并不给它指定区域,下面例子演示建立一个新区块Pending Users不自动激活也没给它指定区域,
$block['pending_users'] = array(
  'info' => t('Pending Users'),
  'status' => FALSE,
  'weight' => 0,
);

区块可见性例子

在区块管理员接口中,你能在区块配置页面的“Page visibility settings”节输入PHP代码片段,当页面建立时,Drupal将允许这个PHP片段来决定区块是否显示;下面是一些常用片段例子,每一个片段应该返回TRUE或FALSE来指示区块为特别需求显示。

      只为登录用户显示

当$user->uid不为0时返回TRUE

<?php
  global $user;
  return (bool) $user->uid;
?>

      只为匿名用户显示

<?php
  global $user;
  return !(bool) $user->uid;
?>

小结

在本章你学到了下面知识:

      + 区块是什么和节点有什么不同
      + 怎样可视化和布置区块
      + 怎样定义一个或多个区块
      + 怎样默认激活一个区块