Thứ Năm, 22 tháng 5, 2014

Understanding Events and Triggers [Hooks, Actions, and Triggers]

Drupal proceeds through a series  of events as it goes about its business. These  internal events are times when modules are allowed to
interact with Drupal’s processing. Table 3-1 shows some of Drupal’s events.


Table 3-1. Examples of Drupal Events
Event
Type
Creation of a node
Node
Deletion of a node
Node Viewing of a node 
Node Creation of a user  account 
Logout  User
User Login User
User Updating of a user profile

Drupal developers refer to these internal events as hooks  because when one of the events occurs, Drupal allows modules to hook 
into the path of execution at that point. You’ve already met some hooks in previous chapters. Typical module development involves
deciding which Drupal event you want  to react to, that is, which hooks you want  to implement in your module.

Suppose you have a web site that is just starting out, and  you are serving the site from the computer in your basement. Once  thesite gets popular, you plan  to sell it to a huge  corporation an get filthy rich. In the meantime, you’d like to be notifiedeach  tim a user logs in. You decide that when a user logs in, you want  the computer to beep. Because your cat is sleeping and  would find
the beeps annoying, you decide to simulate the beep for the tim being with a simple log entry. You quickly  write an.info file and
place  it at sites/all/modules/custom/beep/beep.info:

name = Beep
description = Simulates   a  system  beep.
package  = Pro Drupal Development core  = 7.x
files[] = beep.module

Then  its time  to write sites/all/modules/custom/beep/beep.module:
 <?php
/**
*   @file
*   Provide  a  simulated   beep.
*/
function  beep_beep() watchdog('beep', 'Beep!');
}
This writes  the message “Beep!” to Drupal’s log good enough for now. Next, it’s time  to tell Drupal to beep when a user logs in. We can do that easily by implementing hook_user_login() in our module:
 /**
*   Implementation  of  hook_user_login().
*/
function  beep_user(&$edit, $account) beep_beep();}
There—that was easy. How about beeping when new content is added, too?We can do that by implementing hook_node_insert()
in our module and  catching the insert operation:
 /**
*   Implementation  of  hook_node_insert().
*/
function  beep_node_insert($node) beep_beep();}

What if we wanted a beep when a comment is added? Well, we could implement hook_comment_ insert() and  catch the insert
operation, but lets stop  and  think for a minute. We’re essentially doing the same thing over and  over. Wouldn’t it be nice to have a graphical user  interface where we could associate the action of beeping with whatever hook  and  whatever operation we’d like?That’s what Drupal’s built in trigger  module does.  It allows you to associate some action with a certain event. In thcode, an event is defined as a unique hoooperation combination, such as “user hook,  login operation” or “node hook,  insert operation.” When 
each of these operations occurs, trigger.module lets you triggean action.
To avoid confusion, lets clarify our terms:

     Event: Used in the generic programming sense, this term is generally understood as a message sent  from one component of a system to other components.
      Hook: This programming technique, used in Drupal, allows modules to “hook into”  the flow of execution. There are unique hooks for each  operation that is performed on the “hookable” object (e.g., hook_node_insert).
      Trigger: This refers to a specific combination of a hoo and  an operation with which one or more actions can be associated. For example, the action of beeping can be associated with the login operation of the user hook. 

Understanding Actions


An action  is something that Drupal  does.  Here are some examples:

     Promoting a node to the fron page

     Changing a node from unpublished to published

     Deleting a user

     Sending an e-mail message

Each of these cases has a clearly defined task. Programmers will notice the similarity to PHP functions in the preceding list. For example, you could send e- mail by calling the drupal_mail() function in includes/mail.incActions sound similar to  functions, becauseactionare functions. They are functions that Drupal can introspect and  loosely couple with events (more on that in a moment).
Now, let’s examine the trigger  module. 

The Trigger User Interface

Click the Modules link in the menu at the top of the page,  and  on the Modules page,  enable the trigger module. Then  click the
Structure link in the menu at the top of the page,  and  on the Structure page,  click the Triggers link. You should see an interface
similar to the one shown in Figure  3-1.


Figure 3-1. The trigger assignment interface

Notice the tabs  across the top. Those correspond to Drupal hooks! In Figur3- 1, we are looking at the operations for the node
hook. They’ve all been given nice names; for example, the delete operation of the node hook  is labeled Trigger: Afterdeleting
content.” So each  of the hook’s operations is shown with the ability to assign an action, such as “Publish Content,” when that
operation happens. Each action that is available is listed  in the Choose an action” drop-down.
  • Note Not all actions are available for all triggers, because some actions do not make sense in certain contexts. For example, you wouldn’t run the “Promote post to front page” action with the trigger “After deletingcontent.”Depending on your installation, some triggers may display “No actions available for this trigger.”

Some trigger  names and  their respective hooks and  operations are shown in Table 3-2.

Table 3-2. How Hooks and Triggers Relate in Drupal 7
Hook
Trigger Name
comment_insert   
After saving a new comment
comment_update     
After saving an updated comment
comment_delete 
After deleting a comment
comment_vew 
When  a comment is being viewed  by an authenticated user
cron 
When  cron runs
node_presave
When  either saving a new 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
node_view
When  content is viewed  by an authenticated user 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 userhas logged in
user_logout
After a user has logged out
user_view
When  a user’s profile  is being viewed

Your First Action


What do we need to do in order for our beep function to become a full-fledged action? There are two steps:

1.       Inform Drupal which triggers the action should support.

2.       Create your action function.

The first step  is accomplished by implementing hook_action_info(). Here’s how it should look for our beep module:

/**
*Implemenation  of  hook_action_info().
*/
function beep_action_info()  { return  arry('beep_beep_action' => array( 'type' => 'system','label' => t('Beep
annoyingly'), 'configurable' => FALSE,'triggers' => array('node_view', 'node_insert',  'node_update', 'node_delete'),),
);}

The function name is beep_action_info(), because like other hook  implementations, we use our module name (beep) plus the
name of the hook  (action_info). We’ll be returning an array with an entry for each  action in our module. We are writing
only oneaction, so we have only one entry, keyed by the name of the function that will perform the action: beep_beep_action(). Its handy to know when a function is an action while reading through code,  so we append _action to the name of our
beep_beep() functionto come up with beep_beep_action()Let’s take a closer  look at the keys in our array.

     type: This is the kind of action you are writing. Drupal uses this information to categorize actions in the drop-down select  box of the trigger  assignment user interface. Possible types  include system, node, user, comment, and 
taxonomy. Agood question to ask when determining what  type of action you are writing is, “What object does  this action
work with?” (If the answer is unclear or “lots of different objects!” use the system type.)
     label: This is the friendly name of the action that will be shown in the drop- down select  box of the trigger  assignment
user interface.
     configurable: This determines whether the action takes any parameters.
     triggers: In this array of hooks, each  entry must enumerate the operations the action supports. Drupal uses this
information to determine where it is appropriate to list possible actions in the trigger  assignment user interface.

We’ve described our action to Drupal, so let’s go ahead and  write it:
/**
*   Simulate  a  beep.  A  Drupal action.
*/
function  beep_beep_action() { beep_beep();}

That wasn’t  too difficult, was it? Before continuing, go ahead and  delete beep_user_login() and beep_node_insert(), since  we’ll be using triggers and  actions instead of direct hook  implementations.

Assigning  the Action

Now, lets click the Structure link in the top menu, and  on the Structure page,  click the Triggers  link.
If you’ve done everything correctly, your action should be available in the user interface, as shown in Figure  3-2.



Figure 3-2. The action  should be selectable in the triggers user interface.


Assign the action to the trigger  associated with saving new content by selecting “Beep annoyingly” from the drop down list and 
clicking the Assign button. Next create a new Basic page content item  ansave it. After saving click the Reports link at thetop of
the page and  select  the Recent log entries report. If you set up the action and  trigger  properly, you should see results similar to
Figure  3-3.


Figure 3-3. The results of our beep action being triggered on node save is an entry in the log file.


Changing Which Triggers an Action Supports

If you modify the values  that define which operations this action supports, you should see the availability change in the user
interface. For example, the “Beep” action will be available only to the “After deleting a node” trigger  if you change
beep_action_info() as follows:
 /**
*Implemenation  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'),),);} 

Actions That Support Any Trigger

If you dont want  to restrict your action to a particular trigger  or set of triggers, you can declare that your action supports any
trigger:
/**
*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'),),);}

Advanced Actions

There are essentially two kinds  of actions: actions that take parameters and  actions that do not. The “Beep” action we’ve been
working with does not take any parameters. When  the action is executed, it beeps once and  that’s  the end of it. But there are
manytimes when actions need a bit more context. For example, a “Send e mail” action needs to know to whom to send the e mail anwhat  the subject and message are. An action like that requires some setup in a configuration form and  is calle an advanced action  or a configurable action.

Simple  actions take no parameters, do not require a configuration form, and  are automatically made available by the system.
You tell Drupal that the action you are writing is an advanced action by setting the configurable key to TRUE in your module’s
implementation of hook_action_info(), by providing a form to configure the action, an by providing an optional validation handler and  a required submit handler to process the configuration form. The differences between simple and  advanced actions are
summarized in Table 3-3.

Table 3-3. Summary of How Simple and Advanced Actions Differ

Simple Action
Advanced Action
Parameters
No*
Required
Configuration form
No
Required
Availability
Automatic
Must create instance of action using action administration page
Value of configure key in
hook_action_info()
FALSE  TRUE   

*The $object and $context parameters are available if needed.

Let’s create an advanced action that will beep multiple times. We will be able to specify the number of times that the action will beep using  a configuration form. First, we will need to tell Drupal that this action is configurable. Let’s add an entry for our new action in
the action_info hoo implementation of beep.module:
 /**
*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'),),);}

Let’s quickly  check  if we’ve done the implementation correctly at Administer- > Site configuration- > Actions Sure enough, the
action should show up as a choice in the advanced actions drop-down select box, as shown in Figure  3-4.


Figure 3-4. The new action  appears as a choice.


Now, we need to provide a form so that the administrator can choose how many beeps are desired. We do this by
defining one or more fields using  Drupal’s form API. We’ll also write functions for form validation and  submission. The
names of thefunctions are based on the action’s ID as defined ihook_action_info(). The action ID of the action we are currently discussing is beep_multiple_ beep_action, so convention dictates that we add _form to the form definition
function name to getbeep_multiple_beep_action_form. Drupal expects a validation function named from the action ID
plus_validate (beep_multiple_beep_action_validate) and  a submit function named from the action ID plus_submit (beep_multiple_beep_action_submit).

/**
*   Form  for  configurable Drupal action to  beep  multiple  times
*/
function  beep_multiple_beep_action_form($context) {
$form['beeps'] = array( '#type'   => 'textfield','#title' => t('Number  of  beeps'),'#description' => t('Enter the  number of 
times  to  beep  when this action executes'), '#default_value' => isset($context['beeps']) ?  $context['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  annoying.   Please choose  fewer  than  10 beeps.'));
} else if ((int) $beeps  < 0)  {
form_set_error('beeps', t('That would likely  create a  black  hole!   Beeps  must be  a positive integer.'));}
} 
function  beep_multiple_beep_action_submit($form, $form_state)  
return  array('beeps' => (int)$form_state['values']['beeps']);}

The first function describes the form to Drupal. The only field we define is a singletext field so that the administrator can enter
the number of beeps. To access the advanced actions form, click the Configuration link at the top of the page,  an on the
Configuration page, click the Actions link. On the Actions page,  scroll to the bottom of the page,  and  in the Create an
Advanced action selec list, click the “Beep multiple times” item. After selecting the item,  Drupal displays the advanced actions
form, as shown in Figur 3-5.


Figure 3-5. The action  configuration form for the “Beep multiple times” action


Drupal has added a Description field to the action configuration form. The value of this field is editable and  will be used instead of
the default description that was defined in the action_info hook. That makes sense, because we could create one advancedaction to
beep two times and  give it the description “Beep two times” and  another that beeps five times with the description Beep five times.” That way, we could tell the difference between the two advanced actions when assigning actions to a trigger. Advanced actions can thus be described in a way that makes sense to the administrator.

The validation function is like any other form validation function in Drupal (see Chapter 11 for more on form validation). In this
case, we check  to make sure  the user  has actually entered a number and  that the number is not excessively large.

The submit function’s return value is special for action configuration forms. It should be an array keyed by the fields we are 
interested in. The values  in this array will be made available to the action when it runs. The description is handled automatically, so we need only to return the field we provided, that is, the number of beeps.
Finally, it is time  to write the advanced action itself:
/**
*   Configurable   action. Beeps  a specified number  of  times.
*/
function beep_multiple_beep_action($object, $context)  { for  ($i = 0;  $i < $context['beeps']; $i++) 
{beep_beep();}}

You’ll notice that the action accepts two parameters, $object and  $context. This is in contrast to the simple action we wrote earlier, which used no parameters.

Note Simple actions can take the same parameters as configurable actions. Because PHP ignores parametersthat are passed to a function but do not appear in the function’s signature, we could simply change thefunction signature of our simple
action from beep_beep_action() to beep_beep_action($object,  $context) if we had a need to know something about the current context. All actions are called with the $object and $context parameters.

Using the Context in Actions

We’ve established that the function signature for actions is example_action($object,  $context). Let’s examine each  of those parameters in detail.
      $object: Many actions act on one of Drupal’s built-in objects: nodes, users, taxonomy terms, and  so on. When  an action is
executed by trigger.module, the object that is currently being acted upon is passed along  to the action in the$object parameter. For
example, if an action is set to execute when a new node iscreated, the $object parameter will contain the node object.

     $context: An action can be called  in many different contexts. Actions  declare which triggers they support by defining the hooks
key in hook_action_info(). But actions that support multiple triggers need some way of determining thecontext in which they were
called.  That way, an action can act differently depending on the context.

How the Trigger Module Prepares the Context

Lets set up a scenario. Suppose you are running a web site that presents controversial issues. Here’s the business model: users pay
to register and  may leave only a single comment on the web site. Once they have posted their comment, they are blocked and
must pay again  to get unblocked. Ignoring the economic prospects for such a site, let’s focus on how we could implement this with triggers and  actions.

We will need an action that blocks  the current user.  Examining user.module, we see that Drupal already provides this action for us:
 /**
*   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(),),);}

However, this action does  not show  up on the triggers assignment page,  because they do not declare any supported hooks; the
triggers key is just an empty array.  If only we could change that! But we can.

Changing Existing Actions  with action_info_alter()

When  Drupal runs the action_info hook  so that each  module can declare the actions it provides, Drupal also gives modules a chanceto modify that information including information provided by other modules. Here is how we would make the “Block currentuser”
action available to the comment insert trigger:
 /**
*   Implementation  of  hook_drupal_alter(). Called  by Drupal after
*   hook_action_info() so  modules may  modify  the  action_info array.
*
*   @param  array  $info
*         The result of  calling  hook_action_info() on all  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';}}

The end  result is that the “Block current user action” is now assignable, as shown in Figure  3-6.


Figure 3-6. Assigning the “Block current user” action  to the comment insert trigger

Establishing the Context


Because of the action we have assigned, when a new comment is posted, the current user will be blocked. Let’s take a
closer  look at how that happens. We already know that Drupal’s way of notifying modules that certain events are happening is to fire a hook. In this case, it is the comment hook.  The particular operation that is happening is the insert operation,
since  a new comment is being added. The trigger module implements the comment hook Inside this hook,  it asks the database if there are any actionsassigned to this particular trigger.  The database gives it information about the Block current
user”  action that we assigned. Now the trigger  module gets ready  to execute the action, which has the  standard action
function signature example_action($object,$context).

But we have a problem. The action that is about to be executed is an action of type user, not comment. It expects the
object it receives to be a user object! But here, a user  action is being called  in the context of a comment hook.  Information
about thecomment was passed to the hook,  not informatioabout the user.  What should we do? What actually happens is that the trigger  module determines that our action is a user action and  loads  the $user object that a user action expects.
Here is code  fromodules/trigger/trigger.module that shows how this happens:
 /**
*   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);
 // An  action that   works on users   is being  called in  a  comment  context.case  'user':
return  user_load(is_array($comment) ?  $comment['uid'] : $comment->uid);}}

When  the preceding code  executes for our user  action, the second case matches so the user object is loaded and  then our user
action is executed. The information that the comment hook knows  about (for example, the comment’s subject) is passed along  tthe
action  in the $context  parameter. Note how the action looks for the user’s ID first in the object and  then the context, and  finally
falls back to the global
$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('users')->fields(array('status' => 0))->condition('uid',  $uid)->execute(); drupal_session_destroy_uid($uid);
watchdog('action', 'Blocked   user  %name.', array('%name' => $user->name));}

Actions must be somewhat intelligent, because they do not know much about what  is happening when they are called.  That is whythe best  candidates for actions are straightforward, even atomic. The trigger  module always passes the current hook  andoperation
along  in the context. These  values are stored in $context['hook'] and  $context['op']. This approach offers a standardized way to
provide information to an action. 

How Actions Are Stored

Actions are functions that run  at a given time.  Simple  actions do not have configurable parameters. For example, the “Beep”actionwe created simply beeped. It did not need any other information (though, of course, $object and  $context are available if needed).
Contrast this action with the advanced action we created. The“Beep multiple times” action needed to know how many times to beep.Other advanced actions, such as the “Send e- mail” action, may need even more information: whom to send the e- mail to, what  the subject of the e-mail should be, and  so on. These  parameters must be stored in the database. 

The actions Table

When  an instance of an advanced action is created by the administrator, the information that is entered in the configuration form is
serialized and  saved  into the parameters field of the actions table. A record for the simple “Beep” action would look like this:

aid: 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

Just before an advanced action is executed, the contents of the parameters field are unserialized and included in the $context
parameter that is passed to the action. So the number of beeps in our “Beep multiple times action instance will be available to
beep_multiple_.beep_.action() as $context['beeps']. 

Action IDs

Notice the difference in the action IDs of the two table records in the previous section. The action ID of the simple action is the
actual function name. But obviously we cannot use the function name as an identifier for advanced actions, since  multiple instances ofthe same action are stored. So a numeric action ID (tracked in the actions_aid database table) is used instead.

The actions execution engine determines whether to go through the process of retrieving stored parameters for an action based on
whether the action ID is numeric. If it is not numeric, the action is simply executed and  the database is not consulted. This is avery
quick determination; Drupal uses the same approach in index.php to distinguish content from menu constants.

Calling an Action Directly with actions_do()

The trigger  module is only one way to call actions. You might want  to write a separate module that calls actions and  prepare the parameters yourself. If so, using  actions_do() is the recommended way to call actions. The function signature follows:

actions_do($action_ids,    $object  = NULL,  $context  = NULL,  $a1  = NULL,  $a2  = NULL)

Lets examine each  of these parameters.

     $action_ids: The action(s) to execute, either a single action ID or an array of action IDs

     $object: The object that the action will act upon: a node, user,  or comment, if any

     $context: Associative array containing information the action may wish to use, including configured parameters for advanced actions

     $a1 and $a2: Optional additional parameters that, if passed to actions_do(), will be passed along  to the action

Heres how we would call our simple “Beep” action using  actions_do():

$object  = NULL;  // $object  is a  required  parameter  but  unused  in  this  case actions_do('beep_beep_action',  $object);

And here  is how we would call the “Beep multiple times” advanced action:

$object  = NULL; actions_do(2,  $object);

Or, we could call it and  bypass the retrieval of stored parameters like this:

$object  = NULL;
$context['beeps']  = 5; actions_do('beep_multiple_beep_action',  $object,  $context);





Không có nhận xét nào:

Đăng nhận xét