Chapter 03: Hooks, Actions, and Triggers

Chapter 03: Hooks, Actions, and Triggers | Drupal中国.

当 用Drupal干活时有一个通常的考虑那就是当一个特定的事件产生时有什么事情发生,例如,站点管理员可能想在某条消息发布时接收一个e-mail或者一 个一个用户在评论里包含了某些单词时应该被锁定。本章论述怎样钩进Drupal事件以在这些事件产生时执行你自己的代码。

理解事件和触发器

Drupal通过一连串的事件行进就像它自己的那样,这些内部事件是允许模块与Drupal处理过程交互的时刻,下表展示一些Drupal事件:
Event                                     type
Creation of a node                Node
Deletion of a node                Node
Viewing of a node                 Node
Creation of user account      User
Updating of user profile        User
Login User
Logout User
Drupal开发者认为这些内部事件像钩子一样是因为当一个时间发生时Drupal允许模块在这一点上钩进执行路径,在前一章你已经见到过一些钩子,典型的开发涉及决定哪些Drupal事件是你想做出反应的,就是说,在你的模块中你要实现那个钩子(hook)。
假设你有个刚刚完成的网站,你是在你的地下室计算机上管理你的网站,一旦站点火 了你准备将它卖给大公司而一夜暴富,期间,你在每个用户登录时都想要得到一个通知,你决定在每个用户登录时计算机嘟嘟叫,因为你的猫睡觉了且发现嘟嘟叫太 讨厌,你决定使用一个简单log条目模拟一下嘟嘟叫,于是,你快速地写了一个.info文件,并将其放到sites/all/modules /custom/beep/beep.info中:
name = Beep
description = Simulates a system beep.
packeage = Pro Drupal Development
core = 7.x
files[] = beep.module
然后,写了sites/all/modules/custom/beep/beep.module文件:
<?php
/**
 * @file
 * Provide a simulated beep.
 */
function beep_beep() {
  watchdog('beep', 'Beep!');
}
将一条消息“Beep!”写到Drupal的log中 — 现在就足够了,下一步,是告诉Drupal在用户登录那刻嘟嘟叫的时候啦,我们能用实现hook_user_login()来容易地完成它:
/**
 * Implamentation of hook_user_login().
 */
function beep_user_login(&$editor, $account) {
  beep_beep();
}
这很简单,怎样在一个新内容被增加时嘟嘟一下呢,只需在我们的模块中实现hook_node_insert()捕获 insert 操作:
/**
 * Implamentation of hook_node_insert().
 */
function beep_node_insert($node) {
  beep_beep();
}
那么我们怎样才能让新增个评论也嘟嘟一下呢,好,我们能实现 hook_comment_insert()并捕获 insert 操作,但是等等,想几分钟,我们实质上一遍一遍地做同样的东西,你难道不会提供个图形用户接口吗?在那里你可以将beep分配给你想要的任何钩子和任何操 作类型?这些已经被Drupal内建的模块trigger做了,它允许你去为特定的事件(event)关联某些动作(action)。在这个代码中,一个 事件呗定义为唯一的hook-operation组合,就像“user hook,login operation”或“node hook,insert operation”,当每个这样的操作发生,trigger.module让你触发一个动作。
为了避免混淆,让我们澄清我们的术语:
      
      event: 使用在一般的编程概念中,这个术语通常被理解成从系统的一个组成部分发送到另一组成部分的一条消息。
      hook: 这个编程技术,用在Drupal,允许模块“钩进”执行流,可钩进(hookable)对象上的每个操作都有唯一一个钩子(如hook_node_insert)。
      trigger: 指的是一个钩子和一个操作的特定组合,这个操作(operation)能关联一个或多个动作(action),例如,动作beeping能被关联到user hook的login操作。

理解动作

一个动作就是Drupal做的某些东西,这有些例子:
     
      + 提升一个节点到首页
      将一个节点从未发布状态改到发布状态
      删除一个用户
      发送一个邮件
每一个这样的情况都是一个定义清晰的任务。编程者注意到这与前面提到的PHP函 数类似,例如你能通过调用includes/mail.inc中的drupal_mail()来发送一个e-mail。动作和函数听起来很相似,因为动作 是函数,他们是能使Drupal宽松结合事件的函数(一会再说更多),现在让我们审视trigger模块。  

      Trigger用户接口

点击Modules链接,在模块页面激活trigger模块,然后点击structure链接,进入Structure页面,点击Trigger链接你应该能看到类似图3-1那样的接口:
3-1
Figure 3-1. The trigger assignment interface
注意横跨顶部标签,这与drupal的hooks一致,在图3-1中我们能看见node钩子的操作,它们都有个好看的名字,如 delete 操作显示为“Trigger:After deleting content”这个标签,每个钩子的操作都展示为有能力去关联一个动作,如“Publish Content”,当操作发生时,每个可用动作都显示在“choose an action”的下拉列表里。
注意:不是所有的动作对所有触发器都可用,因为有些动作在特定的内容上没有意义,如,你在触发器”after deleting content”上不能运行“promote post to front page”动作。依赖于你的安装情况,有些触发器可能显示“No actions available for this trigger”。
有些触发器的名字和各自的钩子和操作展示在下表:
Hook                                     Trigger Name
 

comment_insert                    After saving a new comment
comment_update                  After saving an updated comment
comment_delete                   After deleting a comment
comment_view                      When a comment is being viewed by an authenticated user
cron                                      When cron run
node_presave                      When either saving a nwe post or updating an existing post
node_insert                          After saving a new post
node_update                        After saving an updated post
node_delete                         After deleting a post
taxonomy_term_insert          After saving a new term to the database
taxonomy_term_update        After saving an updated term to the database
taxonomy_term_delete         After_deleting a term
user_insert                           After a user account has been created
user_update                         After a user's profile has been updated
user_delete                          After a user has been deleted
user_login                            After a user has logged in
user_logout                          After a user has logged out
user_view                            When a user's profile is being wiewd

      你的第一个动作

按顺序该对我们的beep函数做点什么才能使它成为发育完全的动作,两步:
      1、通知Drupal那个模块应支持这个动作
      2、建立你的活动函数
第一步通过实现hook_action_info()来完成,这里展示beep模块看起来应该怎样:
/**
 * Implementation of hook_action_info().
 */

function beep_action_info() {
  return array(
    'beep_beep_action' => array(
      'type' => 'system',
      'label' => t('Beep annoyingly'),
      'configurable' => FALSE,
      'triggers' => array('node_view', 'node_insert', 'node_update', 'node_delete'),
    ),
  );
}
函数的名字是beep_action_info()因为就是钩子的实现,我们用模块名加上钩子名组成,为我们模块的每一个活动返回一个有一个条目的数组,我们只写了一个动作,还有它只有一条,将执行的动作函数名称作为键:beep_beep_action,便于度代码是知道那个函数是一个动作,所有在我们的beep_beep()函数后面加上_action以示为是个动作。
让我们看看数组的键的意义:
      + type:这是你写的动作的分类,Drupal利用这个在触发器关联用户接口的下拉列表里归类动作。可用的有system node user comment taxonomy。一个好的问题提问是决定你写的动作是什么类型“动作为哪个对象工作?”(如果回答是为大量不同的对象工作,那就把它归为system类型)
      + label:动作的用户友好名字,展示在在触发器关联用户接口的下拉列表里
      + configurable:决定动作是否带任何参数
      + triggers:在这个钩子数组中,每个项目必须是枚举动作支持的操作,Drupal使用这个信息决定在哪个适当的地方
展示可用动作列表。
我们的动作是如下这样:
/** 
 * Simulate a beep. A Drupal action.
 */

function beep_beep_action() {
  beep_beep();
}
这太简单了,是不?到前面把beep_user_login()和beep_node_insert()删除,随后我们将使用触发器和活动去代替直接的钩子实现。

      分派动作

现在,让我们点击Structure链接在此页点击Triggers链接,如果你做的每件事都正确,那么你的活动应该可用啦,就像图3-2:
3-2
Figure 3-2. The action should be selectable in the triggers user interface.
点击“Assign”按钮、从下拉菜单中选择“Beep annoyingly”、保存新的内容,指派动作和触发器关联,下一步建立一个Basic Page内容项保存它,点击Reports链接选择Recent log项纪录,如果你设置动作和触发器没问题,你应当看见如图3-3的结果:
3-3
Figure 3-3. The results of our beep action being triggered on node save is an entry in the log file.

      改变一个动作支持的触发器

如果你修改了定义动作支持哪个操作的设定值,你应该在用户接口看看可用性的变化,例如,如果你这样改变了beep_action_info(),那么“Beep”动作就只又在“After deleting a node”操作上可用:
/**
 * Implementation of hook_action_info().
 */

function beep_action_info() {
  return array(
    'beep_beep_action' => array(
      'type' => 'system',
      'label' => t('Beep annoyingly'),
      'configurable' => FALSE,
      'triggers' => array('node_delete'),
    ),
  );
}

      支持所有触发器的动作

如果你不想限制你的动作仅支持确定的触发器或一组触发器,你可以将你的动作声明为支持任何触发器:
/**
 * Implementation of hook_action_info().
 */

function beep_action_info() {
  return array(
    'beep_beep_action' => array(
      'type' => 'system',
      'label' => t('Beep annoyingly'),
      'configurable' => FALSE,
      'triggers' => array('any'),
    ),
  );
}

      高级动作

有实质上分成两类的动作(action):带参数的动作和不带参数的动 作,“Beep”动作不用带任何参数就能工作,这个动作执行时,嘟嘟一次然后就结束了,但是当一个动作需要大量信息是就要有很多项目,比如,一个 “Send e-mail”动作需要知道是谁发的e-mail,主题和内容都是什么。一个需要配置表单进行一些配置的动作叫“advanced action”(高级动作)或“configurable action”(可配置动作)。
简单动作不带参数、不需要配置表单、由系统自动变为可用。在模块的 hook_action_info()的实现中,你将configurable键设置成TRUE就告诉Drupal你写的是高级动作,提供一个表单去配置 动作、提供一个选项校验处理程序和一个请求提交处理程序来处理配置表单,高级动作和简单动作的不同汇总在下表中:
                                                                  Simple Action                                       Advanced Action
parameters No*                                                                                                              Required
Configuration form                                          No                                                           Required
Availability                                                      Automatic                                                Must create instance of action using actions administration page
Value of configure key
in hook_action_info()                                     FALSE                                                     TRUE
*The $object and $context parameters are available if needed.

 

让我们创建一个可以多次嘟嘟的动作,用一个配置表单来指定动作的嘟嘟次数。
/**
 * Implementation of hook_action_info().
 */

function beep_action_info() {
  return array(
    'beep_beep_action' => array(
      'type' => 'system',
      'label' => t('Beep annoyingly'),
      'configurable' => FALSE,
      'triggers' => array('node_view', 'node_insert', 'node_update', 'node_delete'),
    ),
    'beep_multiple_beep_action' => array(
      'type' => 'system',
      'label' => t('Beep multiple times'),
      'configurable' => TRUE,
      'triggers' => array('node_view', 'node_insert', 'node_update', 'node_delete'),
    ),
  );
}
如果我们实现的正确,在Administer->Site configuration->Action,果然能在下拉列表框中可以选择一个高级动作,如图3-4:
3-4
Figure 3-4. The new action appears as a choice.
现在,我们需要提供一个表单时管理员选择他需要嘟嘟几次,我们使用Field API定义一个或多个字段来完成此项工作,我们还要写一些表单校验和提交的函数。函数名称基于在hook_action_info()中定义的动作的 ID,动作ID当前组成是beep_multiple_beep_action,那么惯例规定我们add_form到表单定义函数名称就是 beep_multiple_beep_action_form,Drupal预计校验函数名称是动作ID加 _validate(beep_multiple_beep_action_validate)及提交函数的名称是动作ID加 _submit(beep_miltiple_beep_action_submit)。
/**
 * Form for configurable Drupal action to beep multiple times.
 */

function beep_multiple_beep_action_form($content) {
  $form['beeps'] = array(
    '#type' => 'textfield',
    '#title' => t('Number of beeps'),
    '#description' => t('Enter the number of times to beep when action executes'),
    '#default_value' => isset($content['beeps']) ? $content['beeps'] ; '1',
    '#required' => TRUE,
  );
  return $form;
}
function beep_multiple_beep_action_validate($form, $form_state) (
  $beeps = $form_state['values']['beeps'];
  if (!is_int($beeps)) {
    form_set_error('beeps', t('Please enter a whole number between 0 and 10.'));
  }
  else if ((int) $beeps > 10) {
    form_set_error('beeps', t('That would be too abboying. Please choose fewer than 10 beeps.'));
  } else if ((int) $beeps < 0) {
    fomr_set_error('beeps', t('That would likely create a block hole! Beeps must a positive integer.'));
  }
} 

functionbeep_multiple_beep_action_submit($form, $form_state) {
  return array(
    'beeps' => (int)$form_state['values']['beeps']
  );
}
第一个函数描述一个表单,只有一个字段允许管理员输入嘟嘟的次数。要访问高级动 作表单,路径为:Configuration->Action,在这个动作页面,滚动到下部有一个高级动作选择列表,点击“Beep multiple times”项,然后选择这项,Drupal将显示一个如图3-5的高级动作表单。
3-5
Figure 3-5. The action configuration form for the “Beep multiple times” action
Drupal有一个附加的动作配置表单描述字段,这个字段的值是可编辑的用来替 代action_info钩子中定义的那个。至于这种方式,因为我们能建立一个高级动作来嘟嘟两次并给它一个描述“Beep two times”而其它那些嘟嘟五次的则带有“Beep five times”,这种方式,我们为一个触发器指派动作时能告诉两个高级动作之间的不同。高级主题因此能以此方式制造管理员感觉。
tip:这两个动作,“Beep two times”和“Beep five times”能同样被称为“Beep multiple times”动作的实例。
校验函数就如同其它的表单校验函数(更多请看第11章)。在此我们检查并确信用户有效输入了一个没超过范围的数字。
提交函数返回一个动作配置表单指定的值,它应该是一个我们感兴趣的字段的键化数组,数组的值应该是在动作运行时称为可用的,描述是自动处理的,我们只需要去返回我们提供的字段,在这里是嘟嘟的次数。
最终现在是写高级动作自己的时候了:
/**
 * COnfigurable action. Beeps a specified number of times.
 */

function beep_multiple_beep_action($object, $context) {
  for ($i = 0; $i < $context['beeps']; $i++) {
    beep_beep();
  }
}
你注意到这个动作接收两个参数 $object和$context,这是参照早先写的没带参数的简单动作。
注意:简单动作也可以想可配置动作那样带同样的参数,因为PHP忽略传递过来但 是在函数体声明中不出现的参数。如果我们想了解关于当前上下文的一些东西,我们能简单改变简单动作的函数声明,将beep_beep_action()改 为beep_beep_action($object, $context)就行,所有动作都带有$object和$context参数运行。

使用上下文动作

我们明确了一个动作的函数声明是exmple_action($object, $context),让我们审查一下每个参数的细节:
      + $object: 许多动作作用与一个Drupal内建的对象之上:节点、用户、taxonomy等等。当trigger.module执行一个动作时,动作作用之上的对象 也在参数$object中传递给动作。例如如果一个动作设置成在一个新节点建立后执行,$object参数将包含节点对象。
      + $context: 一个动作能在不同的上下文中北调用。动作通过在hook_action_info()中定义hooks键来声明支持哪个触发器,但是,支持多触发器的动作 需要通过确定哪个触发器上下文的方式来运行,这种方式向,动作依赖不同的上下文有不同的活动。

      触发器模块如何布置上下文

让我们设计一个场景,假设你有个网站提供了一个争端,这有一个商业模块:用户付款注册,许多只是在站点上留下一条评论,一旦他们贴了一条评论,就锁定他们,只有再次付款才能解锁。忽略经济前景,我们只把焦点放到我们怎么能用触发器和动作实现实现这些。
我们需要一个锁定当前用户的动作,仔细查看user.module就发现Drupal已经给我们提供了一个:
/**
 * Implements hook_action_info().
 */

function user_action_info() {
  return array(
    'user_block_user_action' => array(
      'label' => t('Block current user'),
      'type' => 'user',
      'configurable' => FALSE,
      'triggers' => array(),
    ),
  );
}
这个动作不会通过触发器页面显示出来,因为它声明为不支持任何钩子,triggers键只是一个空数组,只要我们能改变它,我们真能。

      使用action_info_alter()改变存在的动作

当Drupal运行每个模块用来声明它们提供的动作的action_info钩 子时,Drupal还给模块提供一个修改信息的机会 — 包括其他模块提供的信息,这里展示我们怎么样使“Block current user”动作在comment insert触发器中可用:
/**
 * Implementation of hook_drupal_alter(). Called by Drupal after
 * hook_action_info() so modules may modify the action_info array.
 *
 * @param array $info
 *   The relust of calling hook_action_info() on modules.
 */

function beep_action_info_alter(&$info) {
  // Make the "Block current user" action available to the
  // comment insert trigger.

  if (!in_array("comment_insert", $info['user_block_user_action']['triggers'])) {
    $info['user_block_user_action']['triggers'][] = 'comment_insert';
  }
}
最后结果是“Block current user action”现在可以指派,就像图3-6那样。
3-6
Figure 3-6. Assigning the “Block current user” action to the comment insert trigger

建立上下文

因为我们指派的动作,当一个能的评论发布时,当前用户将被锁定。让我们看看其中 发生了什么。我们已经知道Drupal通知模块的方式,某些事件发生将引发一个钩子,在此情形下,这是一个comment钩子,特定的操作发生,这里是 insert操作,随后一个新的评论增加了,trigger模块实现comment钩子,在此钩子中,它询问数据库是否给此特定的触发器指派了动作,数据 库给了它关于我们指派的“Block current user”动作的信息,现在trigger模块准备好去执行这个动作,这个动作有标准的动作函数声明 example_action($object, $context)。
但是我们有一个问题,这个活动作为一个user类型的动作执行的,不是 comment类型,它期望它接收的对象是user对象!但是在这,一个user动作是在comment钩子的上下文中调用的,关于comment的信息 而不是关于user的信息将传递给钩子,我们应该怎么做?究竟发生了什么使trigger模块确定我们的动作是user动作并且载入user动作期望 的$user对象?这是modules/trigger/trigger.module中的一块代码,给我么展示发生了什么:
/** 
 * Loads associated objects for comment triggers. 
 * 
 * When an action is called in a context that does not match its type, the 
 * object that the action expects must be retrieved. For example, when an action 
 * that works on nodes is called during the comment hook, the node object is not 
 * available since the comment hook doesn't pass it. So here we load the object 
 * the action expects. 
 * 
 * @param $type 
 *   The type of action that is about to be called. 
 * @param $comment 
 *   The comment that was passed via the comment hook. 
 * 
 * @return 
 *   The object expected by the action that is about to be called.
 */

function _trigger_normalize_comment_context($type, $comment) {
  switch ($type) {
    // An action that works with nodes is being called in a comment context.
    case 'node' :
      return node_load(is_array($comment) ? $comment['nid'] : $comment->nid);
    case 'user' :
      return user_load(is_array($comment) ? $comment['uid'] : $comment->uid);
  }
}
当上面的代码为我们的user动作执行时,第二种情况匹配,user对象被载入 然后我们的用户动作被执行。comment钩子知道的信息(如comment的主题)存于$context参数值传递给动作。注意动作寻找用户ID,首先 在对象中,然后在上下文,最终会退到全局变量$user:
/**
 * Blocks the current user
 *
 * @ingroup actions
 */ 

function user_block_user_action(&$entity, $context = array()) {
  if (isset($entity->uid)) {
    $uid = $entity->uid;
  }
  elseif (isset($context['uid'])) {
    $uid = $context['uid'];
  }
  else {
    global $user;
    $uid = $user->uid;
  } 

  db_update('user')
    ->field(array('status' => 0))
    ->condition('uid', $uid)
    ->execute();
  drupal_session_destroy_uid($uid);
  watchdog('action', 'Blocked user %name.', array('%name' => $user->name));
}
动作应该有点智能,因为它们不知道多少有关它被调用时发生什么,这就是为什么最 好的动作候选人是直截了当的、原子的。tigger模块总是在上下文中传递当前钩子和操作,这些值存在$context[‘hook’] 和$context[‘op’]中,这种技术提供了一个标准化的方式去为一个动作提供信息。

动作的存储

动作是在给定的时间运行的函数。简单动作没有配置参数,例如我们建立的简单的 “Beep”动作,它不需要其它信息(当然,如果需要,$object和$context也可用)。对比这个动作,我们建立的“Beep multiple times”动作需要知道嘟嘟多少次,其它高级动作,诸如“Send E-mail”,可能需要更多的信息:发给谁,e-mail的主题是什么等等,这些参数应存到数据库中。
      actions表
当一个高级动作实例被管理员建立后,他在配置表单输入的信息被序列化并存入actions表的parameters字段。一个简单动作“Beep”的记录看起来这样:
aid: 2
type: 'system'
callback: 'beep_beep_action'
parameters: (serialized array containing the beeps parameter with its value, i.e., the number of times to beep)
label: Beep three times
只有在一个高级动作被执行前,parameters字段的内容才被反序列化且包 含进$context参数传递给动作,在我们“Beep multiple times”动作实例中的嘟嘟次数将作为$context[‘beeps’]可以在beep_multiple_beep_action()中使用。

      动作ID

注意前一节两个表内的动作ID内的不同,简单动作的iD是实际函数名,但是我们明显我们不能用函数名作为高级动作的标识符,因为相同动作的多个实例都存储了,所以用一个数字动作ID来代替。
动作引擎决定是否去为一个动作处理和抽取参数基于动作ID是否是数字。如果不是数字,动作就简单执行,不询问数据库,这是一个极快速决定,Drupal在index.php中以相同的情形从菜单常量中去内容。

直接用actions_do()调用一个动作

trigger模块只有一种方式去调用动作,你可能想写一个独立的模块,它调用动作并自己准备参数,如果这样,使用actions_do()是被推荐的调用动作的方式。函数声明如下:
actions_do($action_ids, $object = NULL, $context = NULL, $a1 = NULL, $a2 =NULL)
让我们检验这些参数
      + $action_ids: 要执行的动作,可以是单个动作ID或一个动作ID的数组
      + $object: 动作作用其上的对象:一个node、user或comment或any
      + $context: 包含动作希望使用信息的关联数组,为高级数组包含配置参数
      + $a1和$a2: 可选的附加参数,如果传给actions_do(),那么将直接传给动作
这里演示怎样用actions_do()调用简单动作“Beep”:
$object = NULL; // $objects a required parameter but unsed in the case $object是必须得参数,但这种情况不设置它
actions_do('beep_beep_action', $object);
这里演示怎样用actions_do()调用高级动作“Beep multiple times”:
$object = NULL;
actions_do(2, $object);
或者我们能绕开抽取参数来调用它:
$object = NULL;
$context['beeps'] = 5;
action_do('beep_multiple_beep_action', $object, $context);
注意:硬编码的PHP开发者可能迷惑,“为什么全都使用动作?为什么不只是直接 调用函数,或只是实现一个钩子?为什么为上下文藏匿参数而费心,为什么不用传统的PHP参数代替检索它们?”答案是通过一个十分标准的声明格式写出动作, 代码重用能委托给管理员,站点管理员可能不知道PHP,不能调用一个PHP开发者设置的功能去在新节点增加后发送E-mail,管理员简单地挂上 “Send mail”动作到一个触发器,然后当一个新节点增加后触发,但不调用任何东西。

使用hook_trigger_info()定义你自己的触发器

Drupal怎样知道那些触发器可用来显示在触发器用户接口页面?典型方式是,它让模块定义钩子声明那些触发器模块实现。例如,这是一个triggers模块自己的hook_trigger_info()的实现,定义了所有安装了drupal7后可用的标准触发器:
/** 
 * Implements hook_trigger_info(). 
 * 
 * Defines all the triggers that this module implements triggers for. 
 */ 

function trigger_trigger_info() { 
   return array( 
     'node' => array( 
       'node_presave' => array( 
         'label' => t('When either saving new content or updating existing content'), 
       ), 
       'node_insert' => array(
         'label' => t('After saving new content'),
       ),
       'node_update' => array(
         'label' => t('After saving updated content'),
       ), 
       'node_delete' => array( 
         'label' => t('After deleting content'),
       ), 
       'node_view' => array( 
         'label' => t('When content is viewed by an authenticated user'), 
       ), 
     ), 
     'comment' => array( 
       'comment_presave' => array( 
         'label' => t('When either saving a new comment or updating an existing comment'), 
       ), 
       'comment_insert' => array( 
         'label' => t('After saving a new comment'), 
       ), 
       'comment_update' => array( 
         'label' => t('After saving an updated comment'),
       ), 
       'comment_delete' => array( 
         'label' => t('After deleting a comment'), 
       ), 
       'comment_view' => array( 
         'label' => t('When a comment is being viewed by an authenticated user'), 
       ), 
     ), 
     'taxonomy' => array( 
       'taxonomy_term_insert' => array(
         'label' => t('After saving a new term to the database'),
       ),
       'taxonomy_term_update' => array(
         'label' => t('After saving an updated term to the database'),
       ),
       'taxonomy_term_delete' => array(
         'label' => t('After deleting a term'),
       ),
     ), 
     'system' => array( 
       'cron' => array( 
         'label' => t('When cron runs'), 
        ), 
     ), 
     'user' => array( 
       'user_presave' => array(
         'label' => t('When either creating a new user account or updating an existing'), 
       ), 
       'user_insert' => array(
         'label' => t('After creating a new user account'),
       ),
       'user_update' => array(
         'label' => t('After updating a user account'),
       ),
       'user_delete' => array(
         'label' => t('After a user has been deleted'),
       ),
       'user_login' => array(
         'label' => t('After a user has logged in'),
       ),
       'user_logout' => array(
         'label' => t('After a user has logged out'),
       ),
       'user_view' => array(
         'label' => t("When a user's profile is being viewed"),
       ),
     ),
   );
}
正如你在这个函数结构中看见,每一个trigger的分类(如node、 comment、system、和user)返回一个选项数组,呈现在触发器配置页。在每个分类数组内部,你能定义trigger和呈现在触发器配置页面 上的标签,就像下面,在这node_insert是触发器的名字并且label元素的值是呈现在触发器配置页面上的。
'node_insert' => array(
  'label' => t('After saving new content'),
)
如果我们更新第二章的annotate模块去包含钩子,那些钩子看起来这样:
/**
 * Implementation of hook_trigger_info().
 */
function annotate_trigger_info() {
  return array(
    'annotate' => array(
      'annotate_insert' => array(
        'label' => t('After Saving new aanotations'),
      ),
      'annotate_update' => array(
        'label' => t('After saving update annotation'),
      ),
      'annotate_dalete' => array(
        'label' => t('After deleting annotation'),
      ),
      'annotate_view' => array(
        'label' => t('When annotation is viewed by an authenticated user'),
      ),
    ),
  );
}

在清除缓存之后,Drupal应该应该获得新的 hook_trigger_info()的新实例并且修改触发器页面,包含一个新的Annotation钩子的选项卡,就像图3-7那样。当然,模块还要 能回应用module_invoke()或module_invoke_all()触发的钩子和动作,例如,模块应该需要调用 module_invoke_all(‘annotate_insert’, ‘annotate_update’, ‘annotate_delete’, ‘annotate_view’),然后,它还需要实现hook_annotate_insert、hook_annotate_update、 hook_annotate_delete、和hook_annotate_view并使用actions_do()来触发动作。

3-7

Figure 3-7. The newly defined trigger appears as a tab in the triggers user interface.

为存在的钩子增加触发器

有时你的代码增加了一个新的操作,你可能想为一个存在的钩子增加触发器,例如,你 可能想增加一个由hook_node_archive调用的钩子。假设你写了一个archive模块它获取旧的节点并将其移到一个数据仓库,为此你能定义 一个全新的钩子,这完全恰当。但是这操作是基于一个节点,你应该想去触发hook_node_archive代替触发器接口相同选项卡下所有呈现内容上的 所有触发器,假设你的模块叫“archive”,下列代码会增加一个附加触发器:

/**
 * Implementation of hook_trigger_info()
 */
function archive_trigger_info() {
  return array(
    'node' => array(
      'archive_nodes' => array(
        'label' => t('Archive old nodes'),
      )
    )
  );
}

新的触发器将展现在触发器管理页面触发器列表的末尾,就想图3-8展示的。

3-8

Figure 3-8. The additional trigger (“When the post is about to be archived”) appears in the user interface.

小节

阅读完本章,你应该能够:
+ 理解怎样去为一个触发器指派一个动作
+ 写一个简单的动作
+ 写一个高级动作和它的分配配置页面
+ 使用动作管理页面创建和重命名高级动作实例
+ 理解上下文是什么
+ 理解动作怎样能使用上下文来改变它们的行为
+ 理解动作怎样存储、抽取和执行
+ 定义你自己的钩子和使它们像钩子一样显示

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注