Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.666 diff -u -F^f -r1.666 common.inc --- includes/common.inc 28 Jun 2007 07:48:40 -0000 1.666 +++ includes/common.inc 29 Jun 2007 03:29:13 -0000 @@ -2255,6 +2255,7 @@ function _drupal_bootstrap_full() { require_once './includes/image.inc'; require_once './includes/form.inc'; require_once './includes/mail.inc'; + require_once './includes/actions.inc'; // Set the Drupal custom error handler. set_error_handler('drupal_error_handler'); // Emit the correct charset HTTP header. @@ -3480,3 +3481,44 @@ function watchdog_severity_levels() { WATCHDOG_DEBUG => t('debug'), ); } + + +/** + * Explode a string of given tags into an array. + */ +function drupal_explode_tags($tags) { + // This regexp allows the following types of user input: + // this, "somecompany, llc", "and ""this"" w,o.rks", foo bar + $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x'; + preg_match_all($regexp, $tags, $matches); + $typed_tags = array_unique($matches[1]); + + $tags = array(); + foreach ($typed_tags as $tag) { + // If a user has escaped a term (to demonstrate that it is a group, + // or includes a comma or quote character), we remove the escape + // formatting so to save the term into the database as the user intends. + $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag))); + if ($tag != "") { + $tags[] = $tag; + } + } + + return $tags; +} + +/** + * Implode an array of tags into a string. + */ +function drupal_implode_tags($tags) { + $encoded_tags = array(); + foreach ($tags as $tag) { + // Commas and quotes in tag names are special cases, so encode them. + if (strpos($tag, ',') !== FALSE || strpos($tag, '"') !== FALSE) { + $tag = '"'. str_replace('"', '""', $tag) .'"'; + } + + $encoded_tags[] = $tag; + } + return implode(', ', $encoded_tags); +} \ No newline at end of file Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.556 diff -u -F^f -r1.556 comment.module --- modules/comment/comment.module 24 Jun 2007 10:09:52 -0000 1.556 +++ modules/comment/comment.module 29 Jun 2007 03:29:14 -0000 @@ -2086,3 +2086,107 @@ function int2vancode($i = 0) { function vancode2int($c = '00') { return base_convert(substr($c, 1), 36, 10); } + +/** + * Implementation of hook_hook_info(). + */ +function comment_hook_info() { + return array( + 'comment' => array( + 'comment' => array( + 'insert' => array( + 'runs when' => t('When a comment has been created'), + ), + 'update' => array( + 'runs when' => t('When a comment has been updated'), + ), + 'delete' => array( + 'runs when' => t('When a comment has been deleted') + ), + 'view' => array( + 'runs when' => t('When a comment is being viewed') + ), + ), + ), + ); +} + +/** + * 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_NOT_PUBLISHED, $cid); + watchdog('action', 'Unpublished comment %subject.', array('%subject' => $subject)); +} + +function comment_unpublish_by_keyword_action_form($context) { + $form['keywords'] = array( + '#title' => t('Keywords'), + '#type' => 'textarea', + '#description' => t('The comment will be unpublished if it contains any of the character sequences above. Use a comma-separated list of character sequences. Example: funny, bungee jumping, "Company, Inc.".'), + '#default_value' => isset($context['keywords']) ? drupal_implode_tags($context['keywords']) : '', + ); + return $form; +} + +function comment_unpublish_by_keyword_action_submit($form, $form_state) { + return array('keywords' => drupal_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) || strstr($comment->subject, $keyword)) { + db_query('UPDATE {comments} SET status = %d WHERE cid = %d', COMMENT_NOT_PUBLISHED, $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.842 diff -u -F^f -r1.842 node.module --- modules/node/node.module 28 Jun 2007 07:48:40 -0000 1.842 +++ modules/node/node.module 29 Jun 2007 03:29:14 -0000 @@ -616,6 +616,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; @@ -654,7 +656,7 @@ function node_save(&$node) { 'title' => "'%s'", 'body' => "'%s'", 'teaser' => "'%s'", 'timestamp' => '%d', 'uid' => '%d', 'format' => '%d'); - if (!empty($node->log) || $node->is_new || $node->revision) { + if (!empty($node->log) || $node->is_new || (isset($node->revision) && $node->revision)) { // Only store the log message if there's something to store; this prevents // existing log messages from being unintentionally overwritten by a blank // message. A new revision will have an empty log message (or $node->log). @@ -3173,3 +3175,278 @@ function node_forms() { } return $forms; } + +/** + * Implementation of hook_hook_info(). + */ +function node_hook_info() { + return array( + 'node' => array( + 'nodeapi' => array( + 'presave' => array( + 'runs when' => t('When content is about to be saved'), + ), + 'insert' => array( + 'runs when' => t('When content has been created'), + ), + 'update' => array( + 'runs when' => t('When content has been updated'), + ), + 'delete' => array( + 'runs when' => t('When content has been deleted') + ), + 'view' => array( + 'runs when' => t('When content is viewed by an authenticated user') + ), + ), + ), + ); +} + +/** + * Implementation of hook_action_info(). + */ +function node_action_info() { + return array( + 'node_publish_action' => array( + 'type' => 'node', + 'description' => t('Publish post'), + '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 post'), + 'configurable' => FALSE, + 'hooks' => array( + 'nodeapi' => array('presave','insert','update', 'view'), + 'comment' => array('delete','insert','update', 'view'), + ), + ), + 'node_unpublish_by_keyword_action' => array( + 'type' => 'node', + 'description' => t('Unpublish post containing keyword(s)'), + 'configurable' => TRUE, + 'hooks' => array( + 'nodeapi' => array('presave','insert','update', 'view'), + 'comment' => array('delete','insert','update', 'view'), + ), + ), + 'node_make_sticky_action' => array( + 'type' => 'node', + 'description' => t('Make post 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 post 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 post to front page'), + 'configurable' => FALSE, + 'hooks' => array( + 'nodeapi' => array('presave','insert','update', 'view'), + 'comment' => array('delete','insert','update', 'view'), + 'user' => array('login'), + ), + ), + 'node_unpromote_action' => array( + 'type' => 'node', + 'description' => t('Remove post from front 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 post'), + 'configurable' => TRUE, + 'hooks' => array( + 'any' => TRUE, + 'nodeapi' => array('presave','insert','update', 'view'), + 'comment' => array('delete','insert','update', 'view'), + ), + ), + 'node_save_action' => array( + 'type' => 'node', + 'description' => t('Save post'), + 'configurable' => FALSE, + 'hooks' => array( + 'nodeapi' => array('delete','insert','update', 'view'), + 'comment' => array('delete','insert','update', 'view'), + 'user' => array('login'), + ), + ), + ); +} + +/** + * 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', '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', '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', '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', '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(&$node, $context = array()) { + $node->promote = 1; + watchdog('action', '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(&$node, $context = array()) { + $node->promote = 1; + watchdog('action', '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) { + $node->uid = $context['owner_uid']; + $owner_name = db_result(db_query("SELECT name FROM {users} WHERE uid = %d", $context['owner_uid'])); + watchdog('action', 'Changed owner of @type %title to uid %name.', array('@type' => node_get_types('type', $node), '%title' => $node->title, '%name' => $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}")); + $owner_name = ''; + if (isset($context['owner_uid'])) { + $owner_name = db_result(db_query("SELECT name FROM {users} WHERE uid = %d", $context['owner_uid'])); + } + + // 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' => $owner_name, + '#options' => $options, + '#description' => $description, + ); + } + else { + $form['owner_name'] = array( + '#type' => 'textfield', + '#title' => t('Username'), + '#default_value' => $owner_name, + '#autocomplete_path' => 'user/autocomplete', + '#size' => '6', + '#maxlength' => '7', + '#description' => $description, + ); + } + return $form; +} + +function node_assign_owner_action_validate($form, $form_state) { + $count = db_result(db_query("SELECT COUNT(*) FROM {users} WHERE name = '%s'", $form_state['values']['owner_name'])); + if (intval($count) != 1) { + form_set_error('owner_name', t('Please enter a valid username.')); + } +} + +function node_assign_owner_action_submit($form, $form_state) { + // Username can change, so we need to store the ID, not the username. + $uid = db_result(db_query("SELECT uid from {users} WHERE name = '%s'", $form_state['values']['owner_name'])); + return array('owner_uid' => $uid); +} + +/** + * Implementation of a configurable Drupal action. + * Saves a node. + */ +function node_save_action($node) { + node_save($node); + watchdog('action', 'Saved @type %title', array('@type' => node_get_types('type', $node), '%title' => $node->title)); +} + +function node_unpublish_by_keyword_action_form($context) { + $form['keywords'] = array( + '#title' => t('Keywords'), + '#type' => 'textarea', + '#description' => t('The node will be unpublished if it contains any of the character sequences above. Use a comma-separated list of character sequences. Example: funny, bungee jumping, "Company, Inc.".'), + '#default_value' => isset($context['keywords']) ? drupal_implode_tags($context['keywords']) : '', + ); + return $form; +} + +function node_unpublish_by_keyword_action_submit($form, $form_state) { + return array('keywords' => drupal_explode_tags($form_state['values']['keywords'])); +} + +/** + * Implementation of a configurable Drupal action. + * Unpublish a node if it contains a certain string. + * + * @param $context + * An array providing more information about the context of the call to this action. + * @param $comment + * A node object. + */ +function node_unpublish_by_keyword_action($node, $context) { + foreach ($context['keywords'] as $keyword) { + if (strstr(node_view(drupal_clone($node)), $keyword) || strstr($node->title, $keyword)) { + $node->status = 0; + watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_get_types('name', $node), '%title' => $node->title)); + break; + } + } +} \ No newline at end of file Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.499 diff -u -F^f -r1.499 system.module --- modules/system/system.module 28 Jun 2007 00:48:26 -0000 1.499 +++ modules/system/system.module 29 Jun 2007 03:29:15 -0000 @@ -2818,3 +2818,316 @@ function system_batch_page() { print theme('page', $output, FALSE, FALSE); } } + +/** + * Implementation of hook_hook_info(). + */ +function system_hook_info() { + return array( + 'system' => array( + 'cron' => array( + 'run' => array( + 'runs when' => t('When cron runs'), + ), + ), + ), + ); +} + +/** + * 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'), + 'taxonomy' => array('insert', 'update', 'delete'), + ), + ), + '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'), + 'taxonomy' => array('insert', 'update', 'delete'), + ) + ), + 'system_goto_action' => array( + 'description' => t('Redirect to URL'), + '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'), + ) + ) + ); +} + +/** + * Return a form definition so the Send email action can be configured. + * + * @param $context + * Default values (if we are editing an existing action instance). + * @return + * Form definition. + */ +function system_send_email_action_form($context) { + // 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' => '%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'] != '%author') { + // We want the literal %author placeholder to be emphasized in the error message. + form_set_error('recipient', t('Please enter a valid email address or %author.', array('%author' => '%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; +} + +/** + * Implementation of a configurable Drupal action. + * Sends an email. + */ +function system_send_email_action($object, $context) { + global $user; + $variables['%site_name'] = variable_get('site_name', 'Drupal'); + + switch ($context['hook']) { + case 'nodeapi': + // Because this is not an action of type 'node' the node + // will not be passed as $object, but it will still be available + // in $context. + $node = $context['node']; + break; + // The comment hook provides nid, in $context. + case 'comment': + $comment = $context['comment']; + $node = node_load($comment->nid); + case 'user': + // Because this is not an action of type 'user' the user + // object is not passed as $object, but it will still be available + // in $context. + $account = $context['account']; + if (isset($context['node'])) { + $node = $context['node']; + } + elseif ($context['recipient'] == '%author') { + // If we don't have a node, we don't have a node author. + watchdog('error', 'Cannot use %author token in this context.'); + return; + } + break; + case 'taxonomy': + $account = $user; + $vocabulary = taxonomy_vocabulary_load($object->vid); + $variables = array_merge($variables, array( + '%term_name' => $object->name, + '%term_description' => $object->description, + '%term_id' => $object->tid, + '%vocabulary_name' => $vocabulary->name, + '%vocabulary_description' => $vocabulary->description, + '%vocabulary_id' => $vocabulary->vid, + ) + ); + break; + default: + // We are being called directly. + $node = $object; + } + + $from = variable_get('site_mail', ini_get('sendmail_from')); + $recipient = $context['recipient']; + + if (isset($node)) { + if (!isset($account)) { + $account = user_load(array('uid' => $node->uid)); + } + if ($recipient == '%author') { + $recipient = $account->mail; + } + } + + $variables['%username'] = $account->name; + + // Node-based variable translation is only available if we have a node. + if (isset($node) && is_object($node)) { + $variables = array_merge($variables, array( + '%uid' => $node->uid, + '%node_url' => url('node/'. $node->nid, array('absolute' => TRUE)), + '%node_type' => node_get_types('name', $node), + '%title' => $node->title, + '%teaser' => $node->teaser, + '%body' => $node->body + ) + ); + } + + $subject = strtr($context['subject'], $variables); + $subject = str_replace(array("\r", "\n"), '', $subject); + $message = strtr($context['message'], $variables); + $body = drupal_html_to_text($message); + + if (drupal_mail('action_send_email', $recipient, $subject, $body, $from )) { + watchdog('action', 'Sent email to %recipient', array('%recipient' => $recipient)); + } + else { + watchdog('error', 'Unable to send email to %recipient', array('%recipient' => $recipient)); + } +} + +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. Not all variables will be available in all contexts.'), + ); + 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()) { + global $user; + $variables = array( + '%site_name' => variable_get('site_name', 'Drupal'), + '%username' => $user->name ? $user->name : variable_get('anonymous', t('Anonymous')), + ); + + // This action can be called in any context, but if placeholders + // are used a node object must be present to be the source + // of substituted text. + switch ($context['hook']) { + case 'nodeapi': + // Because this is not an action of type 'node' the node + // will not be passed as $object, but it will still be available + // in $context. + $node = $context['node']; + break; + // The comment hook also provides the node, in context. + case 'comment': + $comment = $context['comment']; + $node = node_load($comment->nid); + break; + case 'taxonomy': + $vocabulary = taxonomy_vocabulary_load($object->vid); + $variables = array_merge($variables, array( + '%term_name' => $object->name, + '%term_description' => $object->description, + '%term_id' => $object->tid, + '%vocabulary_name' => $vocabulary->name, + '%vocabulary_description' => $vocabulary->description, + '%vocabulary_id' => $vocabulary->vid, + ) + ); + break; + default: + // We are being called directly. + $node = $object; + } + + if (isset($node) && is_object($node)) { + $variables = array_merge($variables, array( + '%uid' => $node->uid, + '%node_url' => url('node/'. $node->nid, array('absolute' => TRUE)), + '%node_type' => check_plain(node_get_types('name', $node)), + '%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']); +} + +/** + * Implementation of a configurable Drupal action. + * Redirect user to a URL. + */ +function system_goto_action_form($context) { + $form['url'] = array( + '#type' => 'textfield', + '#title' => t('URL'), + '#description' => t('The URL to which the user should be redirected. This can be an internal URL like node/1234 or an external URL like http://drupal.org.'), + '#default_value' => isset($context['url']) ? $context['url'] : '', + '#required' => TRUE, + ); + return $form; +} + +function system_goto_action_submit($form, $form_state) { + return array( + 'url' => $form_state['values']['url'] + ); +} + +function system_goto_action($object, $context) { + drupal_goto($context['url']); +} \ No newline at end of file Index: modules/taxonomy/taxonomy.module =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v retrieving revision 1.363 diff -u -F^f -r1.363 taxonomy.module --- modules/taxonomy/taxonomy.module 22 Jun 2007 08:46:15 -0000 1.363 +++ modules/taxonomy/taxonomy.module 29 Jun 2007 03:29:16 -0000 @@ -826,7 +826,7 @@ function taxonomy_node_save($node, $term unset($terms['tags']); foreach ($typed_input as $vid => $vid_value) { - $typed_terms = taxonomy_explode_tags($vid_value); + $typed_terms = drupal_explode_tags($vid_value); $inserted = array(); foreach ($typed_terms as $typed_term) { @@ -1502,7 +1502,7 @@ function _taxonomy_get_tid_from_term($te */ function taxonomy_autocomplete($vid, $string = '') { // The user enters a comma-separated list of tags. We only autocomplete the last tag. - $array = taxonomy_explode_tags($string); + $array = drupal_explode_tags($string); // Fetch last tag $last_string = trim(array_pop($array)); @@ -1526,30 +1526,6 @@ function taxonomy_autocomplete($vid, $st } /** - * Explode a string of given tags into an array. - */ -function taxonomy_explode_tags($tags) { - // This regexp allows the following types of user input: - // this, "somecompany, llc", "and ""this"" w,o.rks", foo bar - $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x'; - preg_match_all($regexp, $tags, $matches); - $typed_tags = array_unique($matches[1]); - - $tags = array(); - foreach ($typed_tags as $tag) { - // If a user has escaped a term (to demonstrate that it is a group, - // or includes a comma or quote character), we remove the escape - // formatting so to save the term into the database as the user intends. - $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag))); - if ($tag != "") { - $tags[] = $tag; - } - } - - return $tags; -} - -/** * Implode a list of tags of a certain vocabulary into a string. */ function taxonomy_implode_tags($tags, $vid = NULL) { @@ -1568,3 +1544,24 @@ function taxonomy_implode_tags($tags, $v } return implode(', ', $typed_tags); } + +/** + * Implementation of hook_hook_info(). + */ +function taxonomy_hook_info() { + return array( + 'taxonomy' => array( + 'taxonomy' => array( + 'insert' => array( + 'runs when' => t('When a new category has been created'), + ), + 'update' => array( + 'runs when' => t('When a category has been updated'), + ), + 'delete' => array( + 'runs when' => t('When a category has been deleted') + ), + ), + ), + ); +} \ No newline at end of file Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.809 diff -u -F^f -r1.809 user.module --- modules/user/user.module 28 Jun 2007 07:48:41 -0000 1.809 +++ modules/user/user.module 29 Jun 2007 03:29:16 -0000 @@ -3200,3 +3200,90 @@ function _user_password_dynamic_validati $complete = TRUE; } } + + + +/** + * Implementation of hook_hook_info(). + */ +function user_hook_info() { + return array( + 'user' => array( + 'user' => array( + 'insert' => array( + 'runs when' => t('When a user account has been created'), + ), + 'update' => array( + 'runs when' => t('When a user account has been updated'), + ), + 'delete' => array( + 'runs when' => t('When a user account has been deleted') + ), + 'login' => array( + 'runs when' => t('When a user has logged in') + ), + 'logout' => array( + 'runs when' => t('When a user has logged out') + ), + 'view' => array( + 'runs when' => t("When a user's account information is displayed") + ), + ), + ), + ); +} + +/** + * 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( + '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( + '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', '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', 'Banned IP address %ip', array('%ip' => $_SERVER['REMOTE_ADDR'])); +} \ No newline at end of file