/[drupal]/drupal/modules/trigger/trigger.module
ViewVC logotype

Contents of /drupal/modules/trigger/trigger.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.53 - (show annotations) (download) (as text)
Fri Nov 6 03:59:06 2009 UTC (2 weeks, 6 days ago) by webchick
Branch: MAIN
Changes since 1.52: +12 -3 lines
File MIME type: text/x-php
#585868 by sun: Provide a generic way for actions to denote that they change a property.
1 <?php
2 // $Id: trigger.module,v 1.52 2009/11/01 12:11:10 dries Exp $
3
4 /**
5 * @file
6 * Enables functions to be stored and executed at a later time when
7 * triggered by other modules or by one of Drupal's core API hooks.
8 */
9
10 /**
11 * Implement hook_help().
12 */
13 function trigger_help($path, $arg) {
14 $explanation = '<p>' . t('Triggers are system events, such as when new content is added or when a user logs in. The trigger module associates these triggers with actions (functional tasks), such as unpublishing content or e-mailing an administrator. The <a href="@url">Actions settings page</a> contains a list of existing actions and provides the ability to create and configure additional actions.', array('@url' => url('admin/config/system/actions'))) . '</p>';
15 switch ($path) {
16 case 'admin/structure/trigger/comment':
17 return $explanation . '<p>' . t('Below you can assign actions to run when certain comment-related triggers happen. For example, you could promote a post to the front page when a comment is added.') . '</p>';
18 case 'admin/structure/trigger/node':
19 return $explanation . '<p>' . t('Below you can assign actions to run when certain content-related triggers happen. For example, you could send an e-mail to an administrator when content is created or updated.') . '</p>';
20 case 'admin/structure/trigger/system':
21 return $explanation . '<p>' . t('Below you can assign actions to run during each pass of a <a href="@cron">cron maintenance task</a>.', array('@cron' => url('admin/reports/status'))) . '</p>';
22 case 'admin/structure/trigger/taxonomy':
23 return $explanation . '<p>' . t('Below you can assign actions to run when certain taxonomy-related triggers happen. For example, you could send an e-mail to an administrator when a term is deleted.') . '</p>';
24 case 'admin/structure/trigger/user':
25 return $explanation . '<p>' . t("Below you can assign actions to run when certain user-related triggers happen. For example, you could send an e-mail to an administrator when a user account is deleted.") . '</p>';
26 case 'admin/help#trigger':
27 $output = '<p>' . t('The Trigger module provides the ability to trigger <a href="@actions">actions</a> upon system events, such as when new content is added or when a user logs in.', array('@actions' => url('admin/config/system/actions'))) . '</p>';
28 $output .= '<p>' . t('The combination of actions and triggers can perform many useful tasks, such as e-mailing an administrator if a user account is deleted, or automatically unpublishing comments that contain certain words. By default, there are five "contexts" of events (Comments, Content, Cron, Taxonomy, and Users), but more may be added by additional modules.') . '</p>';
29 $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@trigger">Trigger module</a>.', array('@trigger' => 'http://drupal.org/handbook/modules/trigger/')) . '</p>';
30 return $output;
31 }
32 }
33
34 /**
35 * Implement hook_menu().
36 */
37 function trigger_menu() {
38 $items['admin/structure/trigger'] = array(
39 'title' => 'Triggers',
40 'description' => 'Configure when to execute actions.',
41 'page callback' => 'trigger_assign',
42 'access arguments' => array('administer actions'),
43 'file' => 'trigger.admin.inc',
44 );
45
46 // We want contributed modules to be able to describe
47 // their hooks and have actions assignable to them.
48 $trigger_info = module_invoke_all('trigger_info');
49 drupal_alter('trigger_info', $trigger_info);
50
51 foreach ($trigger_info as $module => $hooks) {
52 $info = db_select('system')
53 ->fields('system', array('info'))
54 ->condition('name', $module)
55 ->condition('status', 1)
56 ->execute()
57 ->fetchField();
58 if ($info) {
59 $info = unserialize($info);
60 $nice_name = $info['name'];
61 $items["admin/structure/trigger/$module"] = array(
62 'title' => $nice_name,
63 'page callback' => 'trigger_assign',
64 'page arguments' => array($module),
65 'access arguments' => array('administer actions'),
66 'type' => MENU_LOCAL_TASK,
67 'file' => 'trigger.admin.inc',
68 );
69 }
70 }
71 $items['admin/structure/trigger/unassign'] = array(
72 'title' => 'Unassign',
73 'description' => 'Unassign an action from a trigger.',
74 'page callback' => 'drupal_get_form',
75 'page arguments' => array('trigger_unassign'),
76 'access arguments' => array('administer actions'),
77 'type' => MENU_CALLBACK,
78 'file' => 'trigger.admin.inc',
79 );
80
81 return $items;
82 }
83
84 /**
85 * Implement hook_trigger_info().
86 *
87 * Defines all the triggers that this module implements triggers for.
88 */
89 function trigger_trigger_info() {
90 return array(
91 'node' => array(
92 'node_presave' => array(
93 'label' => t('When either saving new content or updating existing content'),
94 ),
95 'node_insert' => array(
96 'label' => t('After saving new content'),
97 ),
98 'node_update' => array(
99 'label' => t('After saving updated content'),
100 ),
101 'node_delete' => array(
102 'label' => t('After deleting content'),
103 ),
104 'node_view' => array(
105 'label' => t('When content is viewed by an authenticated user'),
106 ),
107 ),
108 'comment' => array(
109 'comment_presave' => array(
110 'label' => t('When either saving a new comment or updating an existing comment'),
111 ),
112 'comment_insert' => array(
113 'label' => t('After saving a new comment'),
114 ),
115 'comment_update' => array(
116 'label' => t('After saving an updated comment'),
117 ),
118 'comment_delete' => array(
119 'label' => t('After deleting a comment'),
120 ),
121 'comment_view' => array(
122 'label' => t('When a comment is being viewed by an authenticated user'),
123 ),
124 ),
125 'taxonomy' => array(
126 'taxonomy_term_insert' => array(
127 'label' => t('After saving a new term to the database'),
128 ),
129 'taxonomy_term_update' => array(
130 'label' => t('After saving an updated term to the database'),
131 ),
132 'taxonomy_term_delete' => array(
133 'label' => t('After deleting a term'),
134 ),
135 ),
136 'system' => array(
137 'cron' => array(
138 'label' => t('When cron runs'),
139 ),
140 ),
141 'user' => array(
142 'user_presave' => array(
143 'label' => t('When either creating a new user account or updating an existing'),
144 ),
145 'user_insert' => array(
146 'label' => t('After creating a new user account'),
147 ),
148 'user_update' => array(
149 'label' => t('After updating a user account'),
150 ),
151 'user_delete' => array(
152 'label' => t('After a user has been deleted'),
153 ),
154 'user_login' => array(
155 'label' => t('After a user has logged in'),
156 ),
157 'user_logout' => array(
158 'label' => t('After a user has logged out'),
159 ),
160 'user_view' => array(
161 'label' => t("When a user's profile is being viewed"),
162 ),
163 ),
164 );
165 }
166
167 /**
168 * Gets the action IDs of actions to be executed for a hook.
169 *
170 * @param $hook
171 * The name of the hook being fired.
172 * @return
173 * An array whose keys are action IDs that the user has associated with
174 * this trigger, and whose values are arrays containing the action type and
175 * label.
176 */
177 function trigger_get_assigned_actions($hook) {
178 return db_query("SELECT ta.aid, a.type, a.label FROM {trigger_assignments} ta LEFT JOIN {actions} a ON ta.aid = a.aid WHERE ta.hook = :hook ORDER BY ta.weight", array(
179 ':hook' => $hook,
180 ))->fetchAllAssoc( 'aid', PDO::FETCH_ASSOC);
181 }
182
183 /**
184 * Implement hook_theme().
185 */
186 function trigger_theme() {
187 return array(
188 'trigger_display' => array(
189 'render element' => 'element',
190 'file' => 'trigger.admin.inc',
191 ),
192 );
193 }
194
195 /**
196 * Implement hook_forms().
197 *
198 * We re-use code by using the same assignment form definition for each hook.
199 */
200 function trigger_forms() {
201 $trigger_info = _trigger_get_all_info();
202 $forms = array();
203 foreach ($trigger_info as $module => $hooks) {
204 foreach ($hooks as $hook => $description) {
205 $forms['trigger_' . $hook . '_assign_form'] = array('callback' => 'trigger_assign_form');
206 }
207 }
208
209 return $forms;
210 }
211
212 /**
213 * Loads associated objects for node triggers.
214 *
215 * When an action is called in a context that does not match its type, the
216 * object that the action expects must be retrieved. For example, when an action
217 * that works on users is called during a node hook implementation, the user
218 * object is not available since the node hook call doesn't pass it. So here we
219 * load the object the action expects.
220 *
221 * @param $type
222 * The type of action that is about to be called.
223 * @param $node
224 * The node that was passed via the node hook.
225 *
226 * @return
227 * The object expected by the action that is about to be called.
228 */
229 function _trigger_normalize_node_context($type, stdClass $node) {
230 // Note that comment-type actions are not supported in node contexts,
231 // because we wouldn't know which comment to choose.
232 switch ($type) {
233 // An action that works on users is being called in a node context.
234 // Load the user object of the node's author.
235 case 'user':
236 return user_load($node->uid);
237 }
238 }
239
240 /**
241 * Calls action functions for node triggers.
242 *
243 * @param $node
244 * Node object.
245 * @param $op
246 * Operation to trigger.
247 * @param $a3
248 * Additional argument to action function.
249 * @param $a4
250 * Additional argument to action function.
251 */
252 function _trigger_node(stdClass $node, $hook, $a3 = NULL, $a4 = NULL) {
253 // Keep objects for reuse so that changes actions make to objects can persist.
254 static $objects;
255 // Prevent recursion by tracking which operations have already been called.
256 static $recursion;
257 if (isset($recursion[$hook])) {
258 return;
259 }
260 $recursion[$hook] = TRUE;
261
262 $aids = trigger_get_assigned_actions($hook);
263 if (!$aids) {
264 return;
265 }
266 $context = array(
267 'group' => 'node',
268 'hook' => $hook,
269 );
270
271 // We need to get the expected object if the action's type is not 'node'.
272 // We keep the object in $objects so we can reuse it if we have multiple actions
273 // that make changes to an object.
274 foreach ($aids as $aid => $info) {
275 $type = $info['type'];
276 if ($type != 'node') {
277 if (!isset($objects[$type])) {
278 $objects[$type] = _trigger_normalize_node_context($type, $node);
279 }
280 // Since we know about the node, we pass that info along to the action.
281 $context['node'] = $node;
282 $result = actions_do($aid, $objects[$type], $context, $a3, $a4);
283 }
284 else {
285 actions_do($aid, $node, $context, $a3, $a4);
286 }
287 }
288 }
289
290 /**
291 * Implement hook_node_view().
292 */
293 function trigger_node_view(stdClass $node, $build_mode) {
294 _trigger_node($node, 'node_view', $build_mode);
295 }
296
297 /**
298 * Implement hook_node_update().
299 */
300 function trigger_node_update(stdClass $node) {
301 _trigger_node($node, 'node_update');
302 }
303
304 /**
305 * Implement hook_node_presave().
306 */
307 function trigger_node_presave(stdClass $node) {
308 _trigger_node($node, 'node_presave');
309 }
310
311 /**
312 * Implement hook_node_insert().
313 */
314 function trigger_node_insert(stdClass $node) {
315 _trigger_node($node, 'node_insert');
316 }
317
318 /**
319 * Implement hook_node_delete().
320 */
321 function trigger_node_delete(stdClass $node) {
322 _trigger_node($node, 'node_delete');
323 }
324
325 /**
326 * Loads associated objects for comment triggers.
327 *
328 * When an action is called in a context that does not match its type, the
329 * object that the action expects must be retrieved. For example, when an action
330 * that works on nodes is called during the comment hook, the node object is not
331 * available since the comment hook doesn't pass it. So here we load the object
332 * the action expects.
333 *
334 * @param $type
335 * The type of action that is about to be called.
336 * @param $comment
337 * The comment that was passed via the comment hook.
338 *
339 * @return
340 * The object expected by the action that is about to be called.
341 */
342 function _trigger_normalize_comment_context($type, $comment) {
343 switch ($type) {
344 // An action that works with nodes is being called in a comment context.
345 case 'node':
346 return node_load(is_array($comment) ? $comment['nid'] : $comment->nid);
347
348 // An action that works on users is being called in a comment context.
349 case 'user':
350 return user_load(is_array($comment) ? $comment['uid'] : $comment->uid);
351 }
352 }
353
354 /**
355 * Implement hook_comment_presave().
356 */
357 function trigger_comment_presave($comment) {
358 _trigger_comment($comment, 'comment_presave');
359 }
360
361 /**
362 * Implement hook_comment_insert().
363 */
364 function trigger_comment_insert($comment) {
365 _trigger_comment($comment, 'comment_insert');
366 }
367
368 /**
369 * Implement hook_comment_update().
370 */
371 function trigger_comment_update($comment) {
372 _trigger_comment($comment, 'comment_update');
373 }
374
375 /**
376 * Implement hook_comment_delete().
377 */
378 function trigger_comment_delete($comment) {
379 _trigger_comment($comment, 'comment_delete');
380 }
381
382 /**
383 * Implement hook_comment_view().
384 */
385 function trigger_comment_view($comment) {
386 _trigger_comment($comment, 'comment_view');
387 }
388
389 /**
390 * Calls action functions for comment triggers.
391 *
392 * @param $a1
393 * Comment object or array of form values.
394 * @param $op
395 * Operation to trigger.
396 */
397 function _trigger_comment($a1, $hook) {
398 // Keep objects for reuse so that changes actions make to objects can persist.
399 static $objects;
400 $aids = trigger_get_assigned_actions($hook);
401 $context = array(
402 'group' => 'comment',
403 'hook' => $hook,
404 );
405 // We need to get the expected object if the action's type is not 'comment'.
406 // We keep the object in $objects so we can reuse it if we have multiple
407 // actions that make changes to an object.
408 foreach ($aids as $aid => $info) {
409 $type = $info['type'];
410 if ($type != 'comment') {
411 if (!isset($objects[$type])) {
412 $objects[$type] = _trigger_normalize_comment_context($type, $a1);
413 }
414 // Since we know about the comment, we pass it along to the action
415 // in case it wants to peek at it.
416 $context['comment'] = (object) $a1;
417 actions_do($aid, $objects[$type], $context);
418 }
419 else {
420 actions_do($aid, $a1, $context);
421 }
422 }
423 }
424
425 /**
426 * Implement hook_cron().
427 */
428 function trigger_cron() {
429 $aids = trigger_get_assigned_actions('cron');
430 $context = array(
431 'group' => 'cron',
432 'hook' => 'cron',
433 );
434 // Cron does not act on any specific object.
435 $object = NULL;
436 actions_do(array_keys($aids), $object, $context);
437 }
438
439 /**
440 * Loads associated objects for user triggers.
441 *
442 * When an action is called in a context that does not match its type, the
443 * object that the action expects must be retrieved. For example, when an action
444 * that works on nodes is called during the user hook, the node object is not
445 * available since the user hook doesn't pass it. So here we load the object the
446 * action expects.
447 *
448 * @param $type
449 * The type of action that is about to be called.
450 * @param $account
451 * The account object that was passed via the user hook.
452 * @return
453 * The object expected by the action that is about to be called.
454 */
455 function _trigger_normalize_user_context($type, $account) {
456 // Note that comment-type actions are not supported in user contexts,
457 // because we wouldn't know which comment to choose.
458 switch ($type) {
459 // An action that works with nodes is being called in a user context.
460 // If a single node is being viewed, return the node.
461 case 'node':
462 // If we are viewing an individual node, return the node.
463 if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) {
464 return node_load(array('nid' => arg(1)));
465 }
466 break;
467 }
468 }
469
470 /**
471 * Implement hook_user_login().
472 */
473 function trigger_user_login(&$edit, $account, $category) {
474 _trigger_user('user_login', $edit, $account, $category);
475 }
476
477 /**
478 * Implement hook_user_logout().
479 */
480 function trigger_user_logout($account) {
481 _trigger_user('user_logout', $edit = NULL, $account);
482 }
483
484 /**
485 * Implement hook_user_presave().
486 */
487 function trigger_user_presave(&$edit, $account, $category) {
488 _trigger_user('user_presave', $edit, $account, $category);
489 }
490
491 /**
492 * Implement hook_user_insert().
493 */
494 function trigger_user_insert(&$edit, $account, $category) {
495 _trigger_user('user_insert', $edit, $account, $category);
496 }
497
498 /**
499 * Implement hook_user_update().
500 */
501 function trigger_user_update(&$edit, $account, $category) {
502 _trigger_user('user_update', $edit, $account, $category);
503 }
504
505 /**
506 * Implement hook_user_cancel().
507 */
508 function trigger_user_cancel($edit, $account, $method) {
509 switch ($method) {
510 case 'user_cancel_reassign':
511 case 'user_cancel_delete':
512 _trigger_user('user_delete', $edit, $account, $method);
513 break;
514 }
515 }
516
517 /**
518 * Implement hook_user_view().
519 */
520 function trigger_user_view($account) {
521 _trigger_user('user_view', $edit = NULL, $account, NULL);
522 }
523
524 /**
525 * Calls action functions for user triggers.
526 */
527 function _trigger_user($hook, &$edit, $account, $category = NULL) {
528 // Keep objects for reuse so that changes actions make to objects can persist.
529 static $objects;
530 $aids = trigger_get_assigned_actions($hook);
531 $context = array(
532 'group' => 'user',
533 'hook' => $hook,
534 'form_values' => &$edit,
535 );
536 foreach ($aids as $aid => $info) {
537 $type = $info['type'];
538 if ($type != 'user') {
539 if (!isset($objects[$type])) {
540 $objects[$type] = _trigger_normalize_user_context($type, $account);
541 }
542 $context['account'] = $account;
543 actions_do($aid, $objects[$type], $context);
544 }
545 else {
546 actions_do($aid, $account, $context, $category);
547 }
548 }
549 }
550
551 /**
552 * Calls action functions for taxonomy triggers.
553 *
554 * @param $hook
555 * Hook to trigger actions for taxonomy_term_insert(),
556 * taxonomy_term_update(), and taxonomy_term_delete().
557 * @param $array
558 * Item on which operation is being performed, either a term or
559 * form values.
560 */
561 function _trigger_taxonomy($hook, $array) {
562 $aids = trigger_get_assigned_actions($hook);
563 $context = array(
564 'group' => 'taxonomy',
565 'hook' => $hook
566 );
567 actions_do(array_keys($aids), (object) $array, $context);
568 }
569
570 /**
571 * Implement hook_taxonomy_term_insert().
572 */
573 function trigger_taxonomy_term_insert($term) {
574 _trigger_taxonomy('taxonomy_term_insert', (array) $term);
575 }
576
577 /**
578 * Implement hook_taxonomy_term_update().
579 */
580 function trigger_taxonomy_term_update($term) {
581 _trigger_taxonomy('taxonomy_term_update', (array) $term);
582 }
583
584 /**
585 * Implement hook_taxonomy_term_delete().
586 */
587 function trigger_taxonomy_term_delete($term) {
588 _trigger_taxonomy('taxonomy_term_delete', (array) $term);
589 }
590
591 /**
592 * Implement hook_actions_delete().
593 *
594 * Removes all trigger entries for the given action, when an action is deleted.
595 */
596 function trigger_actions_delete($aid) {
597 db_delete('trigger_assignments')
598 ->condition('aid', $aid)
599 ->execute();
600 }
601
602 /**
603 * Retrieves and caches information from hook_trigger_info() implementations.
604 */
605 function _trigger_get_all_info() {
606 static $triggers = NULL;
607 if( $triggers ) {
608 return $triggers;
609 }
610
611 $triggers = module_invoke_all('trigger_info');
612 drupal_alter('trigger_info', $triggers);
613 return $triggers;
614 }
615

  ViewVC Help
Powered by ViewVC 1.1.2