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 and get filthy rich. In the meantime, you’d like to be notifiedeach time 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 time 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 it’s time to write sites/all/modules/custom/beep/beep.module:
/**
* @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 let’s 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 the code, an event is defined as a unique hook operation combination, such as “user hook, login operation” or “node hook, insert operation.” When
each of these operations occurs, trigger.module lets you trigger an action.
To avoid confusion, let’s 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.
Understanding Actions
An action is something that Drupal does. Here are some examples:
• Promoting a node to the front 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.inc. Actions sound similar to functions, becauseactions are 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 Figure 3- 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(). It’s 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, let’s 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 and save 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 don’t 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 called 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, and 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 hook 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 in hook_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, and 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 select list, click the “Beep multiple times” item. After selecting the item, Drupal displays the advanced actions
form, as shown in Figure 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
Let’s 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 information about 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 from modules/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 to the
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: 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
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)
Let’s 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
Here’s 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