Index: includes/actions.inc =================================================================== RCS file: includes/actions.inc diff -N includes/actions.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ includes/actions.inc 12 Jun 2007 20:06:56 -0000 @@ -0,0 +1,218 @@ + variable_get('actions_max_stack', 35)) { + watchdog('actions', t('Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.'), WATCHDOG_ERROR); + return; + } + $actions = array(); + $available_actions = actions_list(); + $result = array(); + if (is_array($action_ids)) { + $where = ''; + foreach ($action_ids as $action_id) { + if (is_numeric($action_id)) { + $where .= 'OR aid = ' . $action_id . ' '; + } + elseif (isset($available_actions[$action_id])) { + $actions[$action_id] = $available_actions[$action_id]; + } + } + + if ($where) { // We must go to the database to retrieve instance data. + // Strip off leading 'OR '. + $where = $where ? '(' . strstr($where, " ") . ')' : ''; + $result_db = db_query("SELECT * FROM {actions} WHERE $where"); + while ($data = db_fetch_object($result_db)) { + $action_id = $data->action_id; + $actions[$action_id] = $data->params ? unserialize($data->params) : array(); + $actions[$action_id]['function'] = $data->func; + $actions[$action_id]['type'] = $data->type; + } + } + + // Fire actions, in no particular order. + foreach ($actions as $action_id => $params) { + if (is_numeric($action_id)) { // Configurable actions need parameters. + $function = $params['function']; + $context = array_merge($context, $params); + $result[$action_id] = $function('do', $context, $object, $a3, $a4); + } + else { // Singleton action; $action_id is the function name. + $result[$action_id] = $action_id($context, $object, $a3, $a4); + } + } + } + else { // Optimized execution of single action. + if (is_numeric($action_ids)) { // Configurable action; retrieve stored parameters. + $data = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $action_ids)); + $function = $data->func; + $context = array_merge($context, unserialize($data->params)); + $result[$action_ids] = $function($context, $object, $a3, $a4); + } + else { // Singleton action; $action_ids is the function name. + $result[$action_ids] = $action_ids($context, $object, $a4, $a4); + } + } + return $result; +} + + +/** + * Discover all action functions by invoking hook_action_info(). + * + * mymodule_action_info() { + * return array( + * 'mymodule_functiondescription_action' => array( + * 'type' => 'node', + * 'description' => t('Save node'), + * 'configurable' => FALSE, + * 'hooks' => array( + * 'nodeapi' => array('delete','insert','update', 'view'), + * 'comment' => array('delete','insert','update', 'view'), + * ) + * ) + * ); + * } + * + * The description is used in presenting possible actions to the user for + * configuration. The type is used to present these actions in a logical + * grouping and to denote context. Some types are 'node', 'user', 'comment', + * and 'system'. If an action is configurable it will provide form, + * validation and submission functions. The hooks the action supports + * are declared in the 'hooks' array. + * + * @return + * An associative array keyed on function name. The value of each key is + * an array containing information about the action, such as type of + * action and description of the action, e.g., + * + * $actions['actions_node_publish'] = ('description' => 'Publish a node' ... ) + * +*/ +function actions_list() { + static $actions; + if (isset($actions)) { + return $actions; + } + + $actions = module_invoke_all('action_info'); + return $actions; +} + +/** + * Retrieve all action instances from the database. + * Compare with actions_list() which gathers actions from + * the PHP function namespace. The two are synchronized + * by visiting /admin/build/actions (when actions.module is + * enabled) which runs actions_synchronize(). + * + * @return + * Associative array keyed by action ID. Each value is + * an associative array with keys 'function', 'description', + * and 'type'. + */ +function actions_get_all_actions() { + $actions = array(); + $result = db_query("SELECT * FROM {actions}"); + while ($data = db_fetch_object($result)) { + $actions[$data->aid] = array( + 'function' => $data->func, + 'description' => $data->description, + 'type' => $data->type, + 'configurable' => $data->params ? TRUE : FALSE, + ); + } + return $actions; +} + +/** + * Create an associative array keyed by md5 hashes of function names. + * Hashes are used to prevent actual function names from going out into + * HTML forms and coming back. + * + * @param $actions + * An associative array with function names as keys and associative + * arrays with keys 'description', 'type', etc. as values. Generally + * the output of actions_list() or actions_get_all_actions() is given + * as input to this function. + * + * @return + * An associative array keyed on md5 hash of function name. The value of + * each key is an associative array of function, description, and type + * for the action. + */ +function actions_actions_map($actions) { + $actions_map = array(); + foreach ($actions as $func => $array) { + $key = md5($func); + $actions_map[$key]['function'] = isset($array['function']) ? $array['function'] : $func; + $actions_map[$key]['description'] = $array['description']; + $actions_map[$key]['type'] = $array['type']; + $actions_map[$key]['configurable'] = $array['configurable']; + } + return $actions_map; +} + +/** + * Given an md5 hash of a function name, return the function name. + * Faster than actions_actions_map() when you only need the function name. + * + * @param $hash + * MD5 hash of a function name + * + * @return + * Function name + */ +function actions_function_lookup($hash) { + $actions_list = actions_list(); + foreach ($actions_list as $function => $array) { + if (md5($function) == $hash) { + return $function; + } + } + + // Must be an instance; must check database. + $result = db_query("SELECT aid FROM {actions} WHERE params != ''"); + while ($data = db_fetch_object($result)) { + if (md5($data->aid) == $hash) { + return $data->aid; + } + } +} \ No newline at end of file Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.653 diff -u -u -p -r1.653 common.inc --- includes/common.inc 11 Jun 2007 15:21:14 -0000 1.653 +++ includes/common.inc 12 Jun 2007 20:06:57 -0000 @@ -2228,6 +2228,7 @@ function _drupal_bootstrap_full() { require_once './includes/unicode.inc'; require_once './includes/image.inc'; require_once './includes/form.inc'; + require_once './includes/actions.inc'; // Set the Drupal custom error handler. set_error_handler('drupal_error_handler'); // Emit the correct charset HTTP header. Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.552 diff -u -u -p -r1.552 comment.module --- modules/comment/comment.module 11 Jun 2007 14:56:40 -0000 1.552 +++ modules/comment/comment.module 12 Jun 2007 20:06:57 -0000 @@ -2063,3 +2063,91 @@ function int2vancode($i = 0) { function vancode2int($c = '00') { return base_convert(substr($c, 1), 36, 10); } + +/** + * Implementation of hook_action_info(). + */ +function comment_action_info() { + return array( + 'comment_unpublish_action' => array( + 'description' => t('Unpublish comment'), + 'type' => 'comment', + 'configurable' => FALSE, + 'hooks' => array( + 'comment' => array('insert', 'update', 'view'), + ) + ), + 'comment_unpublish_by_keyword_action' => array( + 'description' => t('Unpublish comment containing keyword(s)'), + 'type' => 'comment', + 'configurable' => TRUE, + 'hooks' => array( + 'comment' => array('insert', 'update'), + ) + ) + ); +} + +/** + * Drupal action to unpublish a comment. + * + * @param $context + * Keyed array. Must contain the id of the comment if $comment is not passed. + * @param $comment + * An optional comment object. + */ +function comment_unpublish_action($comment, $context = array()) { + if (isset($comment->cid)) { + $cid = $comment->cid; + $subject = $comment->subject; + } + else { + $cid = $context['cid']; + $subject = db_result(db_query("SELECT subject FROM {comments} WHERE cid = %d", $cid)); + } + db_query('UPDATE {comments} SET status = %d WHERE cid = %d', COMMENT_PUBLISHED, $cid); + watchdog('action', t('Unpublished comment %subject.', array('%subject' => $subject))); +} + +function comment_unpublish_by_keyword_action_form($context) { + if (!module_exists('taxonomy')) { + $form['warning'] = array( + '#prefix' => "
", + '#suffix' => '
', + '#value' => t('This action requires the !taxonomy_module to be enabled.', array('!taxonomy_module' => l('taxonomy module', 'admin/build/modules'))), + '#weight' => -8, + ); + } + $form['keywords'] = array( + '#title' => t('Keywords'), + '#type' => 'textfield', + '#description' => t('The comment will be unpublished if it contains any of the keywords above. Use a comma-separated list of keywords. Example: funny, bungee jumping, "Company, Inc.".'), + '#default_value' => isset($context['keywords']) ? implode(',', $context['keywords']) : '', + ); + return $form; +} + +function comment_unpublish_by_keyword_action_submit($form, $form_state) { + return array('keywords' => taxonomy_explode_tags($form_state['values']['keywords'])); +} + +/** + * Implementation of a configurable Drupal action. + * Unpublish a comment if it contains a certain string. + * + * @param $context + * An array providing more information about the context of the call to this action. + * Unused here since this action currently only supports the insert and update ops of + * the comment hook, both of which provide a complete $comment object. + * @param $comment + * A comment object. + */ +function comment_unpublish_by_keyword_action($comment, $context) { + foreach ($context['keywords'] as $keyword) { + if (strstr($comment->comment, $keyword)) { + db_query('UPDATE {comments} SET status = '. COMMENT_NOT_PUBLISHED .' WHERE cid = %d', $comment->cid); + watchdog('action', 'Unpublished comment %subject.', array('%subject' => $comment->subject)); + break; + } + } +} \ No newline at end of file Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.829 diff -u -u -p -r1.829 node.module --- modules/node/node.module 7 Jun 2007 03:45:57 -0000 1.829 +++ modules/node/node.module 12 Jun 2007 20:06:57 -0000 @@ -613,6 +613,8 @@ function node_load($param = array(), $re * Save a node object into the database. */ function node_save(&$node) { + // Let modules modify the node before it is saved to the database. + node_invoke_nodeapi($node, 'presave'); global $user; $node->is_new = FALSE; @@ -3100,3 +3102,198 @@ function node_forms() { } return $forms; } + +/** + * Implementation of hook_action_info(). + */ +function node_action_info() { + return array( + 'node_publish_action' => array( + 'type' => 'node', + 'description' => t('Publish node'), + 'configurable' => FALSE, + 'hooks' => array( + 'nodeapi' => array('presave','insert','update', 'view'), + 'comment' => array('delete','insert','update', 'view'), + ), + ), + 'node_unpublish_action' => array( + 'type' => 'node', + 'description' => t('Unpublish node'), + 'configurable' => FALSE, + 'hooks' => array( + 'nodeapi' => array('presave','insert','update', 'view'), + 'comment' => array('delete','insert','update', 'view'), + ), + ), + 'node_make_sticky_action' => array( + 'type' => 'node', + 'description' => t('Make node sticky'), + 'configurable' => FALSE, + 'hooks' => array( + 'nodeapi' => array('presave','insert','update', 'view'), + 'comment' => array('delete','insert','update', 'view'), + ), + ), + 'node_make_unsticky_action' => array( + 'type' => 'node', + 'description' => t('Make node unsticky'), + 'configurable' => FALSE, + 'hooks' => array( + 'nodeapi' => array('presave','insert','update', 'view'), + 'comment' => array('delete','insert','update', 'view'), + ), + ), + 'node_promote_action' => array( + 'type' => 'node', + 'description' => t('Promote node to front page'), + 'configurable' => FALSE, + 'hooks' => array( + 'nodeapi' => array('presave','insert','update', 'view'), + 'comment' => array('delete','insert','update', 'view'), + ), + ), + 'node_unpromote_action' => array( + 'type' => 'node', + 'description' => t('Remove post from node page'), + 'configurable' => FALSE, + 'hooks' => array( + 'nodeapi' => array('presave','insert','update', 'view'), + 'comment' => array('delete','insert','update', 'view'), + ), + ), + 'node_assign_owner_action' => array( + 'type' => 'node', + 'description' => t('Change the author of a node'), + 'configurable' => TRUE, + 'hooks' => array( + 'nodeapi' => array('presave','insert','update', 'view'), + 'comment' => array('delete','insert','update', 'view'), + ), + ), + 'node_save_action' => array( + 'type' => 'node', + 'description' => t('Save node'), + 'configurable' => FALSE, + 'hooks' => array( + 'nodeapi' => array('delete','insert','update', 'view'), + 'comment' => array('delete','insert','update', 'view'), + ), + ), + ); +} + +/** + * Implementation of a Drupal action. + * Sets the status of a node to 1, meaning published. + */ +function node_publish_action(&$node, $context = array()) { + $node->status = 1; + watchdog('action', t('Set @type %title to published.', array('@type' => node_get_types('name', $node), '%title' => $node->title))); +} + +/** + * Implementation of a Drupal action. + * Sets the status of a node to 0, meaning unpublished. + */ +function node_unpublish_action(&$node, $context = array()) { + $node->status = 0; + watchdog('action', t('Set @type %title to unpublished.', array('@type' => node_get_types('name', $node), '%title' => $node->title))); +} + +/** + * Implementation of a Drupal action. + * Sets the sticky-at-top-of-list property of a node to 1. + */ +function node_make_sticky_action(&$node, $context = array()) { + $node->sticky = 1; + watchdog('action', t('Set @type %title to sticky.', array('@type' => node_get_types('name', $node), '%title' => $node->title))); +} + +/** + * Implementation of a Drupal action. + * Sets the sticky-at-top-of-list property of a node to 1. + */ +function node_make_unsticky_action(&$node, $context = array()) { + $node->sticky = 0; + watchdog('action', t('Set @type %title to unsticky.', array('@type' => node_get_types('name', $node), '%title' => $node->title))); +} + +/** + * Implementation of a Drupal action. + * Sets the promote property of a node to 1. + */ +function node_promote_action($op, &$node, $context = array()) { + $node->promote = 1; + watchdog('action', t('Promoted @type %title to front page.', array('@type' => node_get_types('type', $node), '%title' => $node->title))); +} + +/** + * Implementation of a Drupal action. + * Sets the promote property of a node to 0. + */ +function node_unpromote_action($op, &$node, $context = array()) { + $node->promote = 1; + watchdog('action', t('Removed @type %title from front page.', array('@type' => node_get_types('type', $node), '%title' => $node->title))); +} + +/** + * Implementation of a configurable Drupal action. + * Assigns ownership of a node to a user. + */ +function node_assign_owner_action(&$node, $context = array()) { + $uid = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $context['owner_name'])); + $node->uid = $uid; + watchdog('action', t('Changed owner of @type %title to uid %name.', array('@type' => node_get_types('type', $node), '%title' => $node->title, '%name' => $context['owner_name']))); +} + +function node_assign_owner_action_form($context) { + $description = t('The username of the user to which you would like to assign ownership.'); + $count = db_result(db_query("SELECT COUNT(*) FROM {users}")); + // Use dropdown for fewer than 200 users; textbox for more than that. + if (intval($count) < 200) { + $options = array(); + $result = db_query("SELECT uid, name FROM {users} WHERE uid > 0 ORDER BY name"); + while ($data = db_fetch_object($result)) { + $options[$data->name] = $data->name; + } + $form['owner_name'] = array( + '#type' => 'select', + '#title' => t('Username'), + '#default_value' => isset($context['owner_name']) ? $context['owner_name'] : '', + '#options' => $options, + '#description' => $description, + ); + } + else { + $form['owner_name'] = array( + '#type' => 'textfield', + '#title' => t('Username'), + '#default_value' => isset($context['owner_name']) ? $context['owner_name'] : '', + '#size' => '6', + '#maxlength' => '7', + '#description' => $description, + ); + } + return $form; +} + +function node_assign_owner_action_validate($form_id, $form_values) { + $count = db_result(db_query("SELECT COUNT(*) FROM {users} WHERE name = '%s'", $context['owner_name'])); + if (intval($count) != 1) { + form_set_error('owner_name', t('Please enter a valid username.')); + } +} + +function node_assign_owner_action_submit($form_id, $form_values) { + return array('owner_name' => $form_values['owner_name']); +} + +/** + * Implementation of a configurable Drupal action. + * Saves a node. + */ +function node_save_action($node) { + node_save($node); + watchdog('action', t('Saved @type %title', array('@type' => node_get_types('type', $node), '%title' => $node->title))); +} \ No newline at end of file Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.491 diff -u -u -p -r1.491 system.module --- modules/system/system.module 8 Jun 2007 12:51:59 -0000 1.491 +++ modules/system/system.module 12 Jun 2007 20:06:57 -0000 @@ -2718,3 +2718,190 @@ function system_batch_page() { print theme('page', $output, FALSE, FALSE); } } + +/** + * Implementation of hook_action_info(). + */ +function system_action_info() { + return array( + 'system_message_action' => array( + 'type' => 'system', + 'description' => t('Display a message to the user'), + 'configurable' => TRUE, + 'hooks' => array( + 'nodeapi' => array('view', 'insert', 'update', 'delete'), + 'comment' => array('view', 'insert', 'update', 'delete'), + 'user' => array('view', 'insert', 'update', 'delete', 'login'), + ), + ), + 'system_send_email_action' => array( + 'description' => t('Send e-mail'), + 'type' => 'system', + 'configurable' => TRUE, + 'hooks' => array( + 'nodeapi' => array('view', 'insert', 'update', 'delete'), + 'comment' => array('view', 'insert', 'update', 'delete'), + 'user' => array('view', 'insert', 'update', 'delete', 'login'), + ) + ) + ); +} + +/** + * Implementation of a configurable Drupal action. + * Sends an email. + */ +function system_send_email_action($object, $context) { + switch($context['hook']) { + case 'node': + $node = $object; + break; + // The comment hook also provides the node, in context. + case 'comment': + $node = $context['node']; + case 'user': + $user = $object; + break; + } + + // Note this is the user who owns the node, not global $user. + if (!isset($user)) { + $user = user_load(array('uid' => $node->uid)); + } + $from = variable_get('site_mail', ini_get('sendmail_from')); + $subject = $context['subject']; + $message = $context['message']; + if ($context['recipient'] == '%author') { + $recipient = $user->mail; + } else { + $recipient = $context['recipient']; + } + $variables['%site_name'] = variable_get('site_name', 'Drupal'); + $variables['%username'] = $user->name; + + // Node-based variable translation is only available if we have a node. + if (isset($node) && is_object($node)) { + $variables = array_merge($variables, array( + '%username' => check_plain($user->name), + '%uid' => $node->uid, + '%node_url' => url('node/' . $node->nid, NULL, NULL, TRUE), + '%node_type' => check_plain($node->type), + '%title' => filter_xss($node->title), + '%teaser' => filter_xss($node->teaser), + '%body' => filter_xss($node->body) + ) + ); + + $subject = strtr($subject, $variables); + $subject = str_replace(array("\r", "\n"), '', $subject); + $message = strtr($message, $variables); + } + if (drupal_mail('action_send_email', $recipient, $subject, $message, "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from" )) { + watchdog('action', t('Sent email to %recipient', array('%recipient' => $recipient))); + } + else { + watchdog('error', t('Unable to send email to %recipient', array('%recipient' => $recipient))); + } +} + +function system_send_email_action_form($context) { + // Return a form definition for the configuration form. + // Set default values for form. + if (!isset($context['recipient'])) $context['recipient'] = ''; + if (!isset($context['subject'])) $context['subject'] = ''; + if (!isset($context['message'])) $context['message'] = ''; + + $form['recipient'] = array( + '#type' => 'textfield', + '#title' => t('Recipient'), + '#default_value' => $context['recipient'], + '#size' => '20', + '#maxlength' => '254', + '#description' => t('The email address to which the message should be sent OR enter %author if you would like to send an e-mail to the original author of the post.', array('%author' => t('%author'))), + ); + $form['subject'] = array( + '#type' => 'textfield', + '#title' => t('Subject'), + '#default_value' => $context['subject'], + '#size' => '20', + '#maxlength' => '254', + '#description' => t('The subject of the message.'), + ); + $form['message'] = array( + '#type' => 'textarea', + '#title' => t('Message'), + '#default_value' => $context['message'], + '#cols' => '80', + '#rows' => '20', + '#description' => t('The message that should be sent. You may include the following variables: %site_name, %username, %node_url, %node_type, %title, %teaser, %body. Not all variables will be available in all contexts.'), + ); + return $form; +} + +function system_send_email_action_validate($form, $form_state) { + $form_values = $form_state['values']; + // Validate the configuration form. + if (!valid_email_address($form_values['recipient']) && $form_values['recipient'] != t('%author')) { + form_set_error('recipient', t('Please enter a valid email address or %author.', array('%author' => theme('placeholder', '%author')))); + } +} + +function system_send_email_action_submit($form, $form_state) { + $form_values = $form_state['values']; + // Process the HTML form to store configuration. The keyed array that + // we return will be serialized to the database. + $params = array( + 'recipient' => $form_values['recipient'], + 'subject' => $form_values['subject'], + 'message' => $form_values['message']); + return $params; +} + +function system_message_action_form($context) { + $form['message'] = array( + '#type' => 'textarea', + '#title' => t('Message'), + '#default_value' => isset($context['message']) ? $context['message'] : '', + '#required' => TRUE, + '#rows' => '8', + '#description' => t('The message to be displayed to the current user. You may include the following variables: %site_name, %username, %node_url, %node_type, %title, %teaser, %body.'), + ); + return $form; +} + +function system_message_action_submit($form, $form_state) { + return array('message' => $form_state['values']['message']); +} + +/** + * Implementation of a configurable Drupal action. + * Sends a configurable message to the current user's screen. + */ +function system_message_action(&$object, $context = array()) { + switch($context['hook']) { + case 'nodeapi': + $node = &$object; + break; + // The comment hook gives us the nid of the node. + case 'comment': + $node = node_load($object['nid']); + } + global $user; + $variables = array( + '%site_name' => variable_get('site_name', 'drupal'), + '%username' => $user->name ? $user->name : variable_get('anonymous', t('Anonymous')), + ); + + if (isset($node) && is_object($node)) { + $variables += array( + '%uid' => $node->uid, + '%node_url' => url('node/' . $node->nid, NULL, NULL, TRUE), + '%node_type' => check_plain($node->type), + '%title' => filter_xss($node->title), + '%teaser' => filter_xss($node->teaser), + '%body' => filter_xss($node->body), + ); + } + $context['message'] = strtr($context['message'], $variables); + drupal_set_message($context['message']); +} \ No newline at end of file Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.796 diff -u -u -p -r1.796 user.module --- modules/user/user.module 11 Jun 2007 15:27:52 -0000 1.796 +++ modules/user/user.module 12 Jun 2007 20:06:57 -0000 @@ -3185,3 +3185,60 @@ function _user_password_dynamic_validati $complete = TRUE; } } + +/** + * Implementation of hook_action_info(). + */ +function user_action_info() { + return array( + 'user_block_user_action' => array( + 'description' => t('Block current user'), + 'type' => 'user', + 'configurable' => FALSE, + 'hooks' => array( + 'user' => array('view', 'insert', 'update', 'delete', 'login', 'logout'), + 'nodeapi' => array('presave', 'delete','insert','update','view'), + 'comment' => array('view', 'insert', 'update', 'delete'), + ), + ), + 'user_block_ip_action' => array( + 'description' => t('Ban IP address of current user'), + 'type' => 'user', + 'configurable' => FALSE, + 'hooks' => array( + 'user' => array('view', 'insert', 'update', 'delete', 'login', 'logout'), + 'nodeapi' => array('presave', 'delete','insert','update','view'), + 'comment' => array('view', 'insert', 'update', 'delete'), + ) + ) + ); +} + +/** + * Implementation of a Drupal action. + * Blocks the current user. + */ +function user_block_user_action(&$object, $context = array()) { + if (isset($object->uid)) { + $uid = $object->uid; + } + elseif (isset($context['uid'])) { + $uid = $context['uid']; + } + else { + global $user; + $uid = $user->uid; + } + db_query("UPDATE {users} SET status = 0 WHERE uid = %d", $uid); + sess_destroy_uid($uid); + watchdog('action', t('Blocked user %name.', array('%name' => check_plain($user->name)))); +} + +/** + * Implementation of a Drupal action. + * Adds an access rule that blocks the user's IP address. + */ +function user_block_ip_action() { + db_query("INSERT INTO {access} (mask, type, status) VALUES ('%s', '%s', %d)", $_SERVER['REMOTE_ADDR'], 'host', 0); + watchdog('action', t('Banned IP %ip', array('%ip' => $_SERVER['REMOTE_ADDR']))); +} \ No newline at end of file