drupal/modules/trigger/trigger.module

597 lines
18 KiB
Plaintext

<?php
// $Id$
/**
* @file
* Enables functions to be stored and executed at a later time when
* triggered by other modules or by one of Drupal's core API hooks.
*/
/**
* Implement hook_help().
*/
function trigger_help($path, $arg) {
$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>';
switch ($path) {
case 'admin/structure/trigger/comment':
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>';
case 'admin/structure/trigger/node':
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>';
case 'admin/structure/trigger/system':
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>';
case 'admin/structure/trigger/taxonomy':
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>';
case 'admin/structure/trigger/user':
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>';
case 'admin/help#trigger':
$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>';
$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>';
$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>';
return $output;
}
}
/**
* Implement hook_menu().
*/
function trigger_menu() {
$items['admin/structure/trigger'] = array(
'title' => 'Triggers',
'description' => 'Configure when to execute actions.',
'page callback' => 'trigger_assign',
'access arguments' => array('administer actions'),
'file' => 'trigger.admin.inc',
);
// We want contributed modules to be able to describe
// their hooks and have actions assignable to them.
$trigger_info = module_invoke_all('trigger_info');
drupal_alter('trigger_info', $trigger_info);
foreach ($trigger_info as $module => $hooks) {
$info = db_select('system')
->fields('system', array('info'))
->condition('name', $module)
->condition('status', 1)
->execute()
->fetchField();
if ($info) {
$info = unserialize($info);
$nice_name = $info['name'];
$items["admin/structure/trigger/$module"] = array(
'title' => $nice_name,
'page callback' => 'trigger_assign',
'page arguments' => array($module),
'access arguments' => array('administer actions'),
'type' => MENU_LOCAL_TASK,
'file' => 'trigger.admin.inc',
);
}
}
$items['admin/structure/trigger/unassign'] = array(
'title' => 'Unassign',
'description' => 'Unassign an action from a trigger.',
'page callback' => 'drupal_get_form',
'page arguments' => array('trigger_unassign'),
'access arguments' => array('administer actions'),
'type' => MENU_CALLBACK,
'file' => 'trigger.admin.inc',
);
return $items;
}
/**
* Implement 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_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_insert' => array(
'label' => t('After a user account has been created'),
),
'user_update' => array(
'label' => t("After a user's profile has been updated"),
),
'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"),
),
),
);
}
/**
* Gets the action IDs of actions to be executed for a hook.
*
* @param $hook
* The name of the hook being fired.
* @return
* An array whose keys are action IDs that the user has associated with
* this trigger, and whose values are arrays containing the action type and
* label.
*/
function trigger_get_assigned_actions($hook) {
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(
':hook' => $hook,
))->fetchAllAssoc( 'aid', PDO::FETCH_ASSOC);
}
/**
* Implement hook_theme().
*/
function trigger_theme() {
return array(
'trigger_display' => array(
'arguments' => array('element' => NULL),
'file' => 'trigger.admin.inc',
),
);
}
/**
* Implement hook_forms().
*
* We re-use code by using the same assignment form definition for each hook.
*/
function trigger_forms() {
$trigger_info = _trigger_get_all_info();
$forms = array();
foreach ($trigger_info as $module => $hooks) {
foreach ($hooks as $hook => $description) {
$forms['trigger_' . $hook . '_assign_form'] = array('callback' => 'trigger_assign_form');
}
}
return $forms;
}
/**
* Loads associated objects for node 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 users is called during a node hook implementation, the user
* object is not available since the node hook call 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 $node
* The node that was passed via the node hook.
*
* @return
* The object expected by the action that is about to be called.
*/
function _trigger_normalize_node_context($type, $node) {
// Note that comment-type actions are not supported in node contexts,
// because we wouldn't know which comment to choose.
switch ($type) {
// An action that works on users is being called in a node context.
// Load the user object of the node's author.
case 'user':
return user_load($node->uid);
}
}
/**
* Calls action functions for node triggers.
*
* @param $node
* Node object.
* @param $op
* Operation to trigger.
* @param $a3
* Additional argument to action function.
* @param $a4
* Additional argument to action function.
*/
function _trigger_node($node, $hook, $a3 = NULL, $a4 = NULL) {
// Keep objects for reuse so that changes actions make to objects can persist.
static $objects;
// Prevent recursion by tracking which operations have already been called.
static $recursion;
if (isset($recursion[$hook])) {
return;
}
$recursion[$hook] = TRUE;
$aids = trigger_get_assigned_actions($hook);
if (!$aids) {
return;
}
$context = array(
'group' => 'node',
'hook' => $hook,
);
// We need to get the expected object if the action's type is not 'node'.
// We keep the object in $objects so we can reuse it if we have multiple actions
// that make changes to an object.
foreach ($aids as $aid => $info) {
$type = $info['type'];
if ($type != 'node') {
if (!isset($objects[$type])) {
$objects[$type] = _trigger_normalize_node_context($type, $node);
}
// Since we know about the node, we pass that info along to the action.
$context['node'] = $node;
$result = actions_do($aid, $objects[$type], $context, $a3, $a4);
}
else {
actions_do($aid, $node, $context, $a3, $a4);
}
}
}
/**
* Implement hook_node_view().
*/
function trigger_node_view($node, $build_mode) {
_trigger_node($node, 'node_view', $build_mode);
}
/**
* Implement hook_node_update().
*/
function trigger_node_update($node) {
_trigger_node($node, 'node_update');
}
/**
* Implement hook_node_presave().
*/
function trigger_node_presave($node) {
_trigger_node($node, 'node_presave');
}
/**
* Implement hook_node_insert().
*/
function trigger_node_insert($node) {
_trigger_node($node, 'node_insert');
}
/**
* Implement hook_node_delete().
*/
function trigger_node_delete($node) {
_trigger_node($node, 'node_delete');
}
/**
* 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);
}
}
/**
* Implement hook_comment_insert().
*/
function trigger_comment_insert($comment) {
_trigger_comment($comment, 'comment_insert');
}
/**
* Implement hook_comment_update().
*/
function trigger_comment_update($comment) {
_trigger_comment($comment, 'comment_update');
}
/**
* Implement hook_comment_delete().
*/
function trigger_comment_delete($comment) {
_trigger_comment($comment, 'comment_delete');
}
/**
* Implement hook_comment_view().
*/
function trigger_comment_view($comment) {
_trigger_comment($comment, 'comment_view');
}
/**
* Calls action functions for comment triggers.
*
* @param $a1
* Comment object or array of form values.
* @param $op
* Operation to trigger.
*/
function _trigger_comment($a1, $hook) {
// Keep objects for reuse so that changes actions make to objects can persist.
static $objects;
$aids = trigger_get_assigned_actions($hook);
$context = array(
'group' => 'comment',
'hook' => $hook,
);
// We need to get the expected object if the action's type is not 'comment'.
// We keep the object in $objects so we can reuse it if we have multiple
// actions that make changes to an object.
foreach ($aids as $aid => $info) {
$type = $info['type'];
if ($type != 'comment') {
if (!isset($objects[$type])) {
$objects[$type] = _trigger_normalize_comment_context($type, $a1);
}
// Since we know about the comment, we pass it along to the action
// in case it wants to peek at it.
$context['comment'] = (object) $a1;
actions_do($aid, $objects[$type], $context);
}
else {
$a1 = (object) $a1;
actions_do($aid, $a1, $context);
}
}
}
/**
* Implement hook_cron().
*/
function trigger_cron() {
$aids = trigger_get_assigned_actions('cron');
$context = array(
'group' => 'cron',
'hook' => 'cron',
);
// Cron does not act on any specific object.
$object = NULL;
actions_do(array_keys($aids), $object, $context);
}
/**
* Loads associated objects for user 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 user hook, the node object is not
* available since the user 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 $account
* The account object that was passed via the user hook.
* @return
* The object expected by the action that is about to be called.
*/
function _trigger_normalize_user_context($type, $account) {
// Note that comment-type actions are not supported in user contexts,
// because we wouldn't know which comment to choose.
switch ($type) {
// An action that works with nodes is being called in a user context.
// If a single node is being viewed, return the node.
case 'node':
// If we are viewing an individual node, return the node.
if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) {
return node_load(array('nid' => arg(1)));
}
break;
}
}
/**
* Implement hook_user_login().
*/
function trigger_user_login(&$edit, $account, $category) {
_trigger_user('user_login', $edit, $account, $category);
}
/**
* Implement hook_user_logout().
*/
function trigger_user_logout($account) {
_trigger_user('user_logout', $edit = NULL, $account);
}
/**
* Implement hook_user_insert().
*/
function trigger_user_insert(&$edit, $account, $category) {
_trigger_user('user_insert', $edit, $account, $category);
}
/**
* Implement hook_user_update().
*/
function trigger_user_update(&$edit, $account, $category) {
_trigger_user('user_update', $edit, $account, $category);
}
/**
* Implement hook_user_cancel().
*/
function trigger_user_cancel($edit, $account, $method) {
switch ($method) {
case 'user_cancel_reassign':
case 'user_cancel_delete':
_trigger_user('user_delete', $edit, $account, $method);
break;
}
}
/**
* Implement hook_user_view().
*/
function trigger_user_view($account) {
_trigger_user('user_view', $edit = NULL, $account, NULL);
}
/**
* Calls action functions for user triggers.
*/
function _trigger_user($hook, &$edit, $account, $category = NULL) {
// Keep objects for reuse so that changes actions make to objects can persist.
static $objects;
$aids = trigger_get_assigned_actions($hook);
$context = array(
'group' => 'user',
'hook' => $hook,
'form_values' => &$edit,
);
foreach ($aids as $aid => $info) {
$type = $info['type'];
if ($type != 'user') {
if (!isset($objects[$type])) {
$objects[$type] = _trigger_normalize_user_context($type, $account);
}
$context['account'] = $account;
actions_do($aid, $objects[$type], $context);
}
else {
actions_do($aid, $account, $context, $category);
}
}
}
/**
* Calls action functions for taxonomy triggers.
*
* @param $hook
* Hook to trigger actions for taxonomy_term_insert(),
* taxonomy_term_update(), and taxonomy_term_delete().
* @param $array
* Item on which operation is being performed, either a term or
* form values.
*/
function _trigger_taxonomy($hook, $array) {
$aids = trigger_get_assigned_actions($hook);
$context = array(
'group' => 'taxonomy',
'hook' => $hook
);
actions_do(array_keys($aids), (object) $array, $context);
}
/**
* Implement hook_taxonomy_term_insert().
*/
function trigger_taxonomy_term_insert($term) {
_trigger_taxonomy('taxonomy_term_insert', (array) $term);
}
/**
* Implement hook_taxonomy_term_update().
*/
function trigger_taxonomy_term_update($term) {
_trigger_taxonomy('taxonomy_term_update', (array) $term);
}
/**
* Implement hook_taxonomy_term_delete().
*/
function trigger_taxonomy_term_delete($term) {
_trigger_taxonomy('taxonomy_term_delete', (array) $term);
}
/**
* Implement hook_actions_delete().
*
* Removes all trigger entries for the given action, when an action is deleted.
*/
function trigger_actions_delete($aid) {
db_delete('trigger_assignments')
->condition('aid', $aid)
->execute();
}
/**
* Retrieves and caches information from hook_trigger_info() implementations.
*/
function _trigger_get_all_info() {
static $triggers = NULL;
if( $triggers ) {
return $triggers;
}
$triggers = module_invoke_all('trigger_info');
drupal_alter('trigger_info', $triggers);
return $triggers;
}