diff options
Diffstat (limited to 'modules/system')
-rw-r--r-- | modules/system/system.info | 1 | ||||
-rw-r--r-- | modules/system/system.module | 150 | ||||
-rw-r--r-- | modules/system/system.test | 57 | ||||
-rw-r--r-- | modules/system/system.tokens.inc | 308 |
4 files changed, 382 insertions, 134 deletions
diff --git a/modules/system/system.info b/modules/system/system.info index 2709050a7..8eb7db900 100644 --- a/modules/system/system.info +++ b/modules/system/system.info @@ -11,4 +11,5 @@ files[] = image.gd.inc files[] = system.install files[] = system.test files[] = system.tar.inc +files[] = system.tokens.inc required = TRUE diff --git a/modules/system/system.module b/modules/system/system.module index 5a63d4a65..14083caba 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -2641,7 +2641,7 @@ function system_send_email_action_form($context) { '#title' => t('Recipient'), '#default_value' => $context['recipient'], '#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 author of the original post.', array('%author' => '%author')), + '#description' => t('The email address to which the message should be sent OR enter [node:author:mail], [comment:author:mail], etc. if you would like to send an e-mail to the author of the original post.'), ); $form['subject'] = array( '#type' => 'textfield', @@ -2656,7 +2656,7 @@ function system_send_email_action_form($context) { '#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, %term_name, %term_description, %term_id, %vocabulary_name, %vocabulary_description, %vocabulary_id. Not all variables will be available in all contexts.'), + '#description' => t('The message that should be sent. You may include placeholders like [node:title], [user:name], and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'), ); return $form; } @@ -2692,59 +2692,14 @@ function system_send_email_action_submit($form, $form_state) { * Implement a configurable Drupal action. Sends an email. */ function system_send_email_action($object, $context) { - global $user; - - switch ($context['hook']) { - case 'node': - // 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); - break; - 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; - default: - // We are being called directly. - $node = $object; + if (empty($context['node'])) { + $context['node'] = $object; } - $recipient = $context['recipient']; - - if (isset($node)) { - if (!isset($account)) { - $account = user_load($node->uid); - } - if ($recipient == '%author') { - $recipient = $account->mail; - } - } - - if (!isset($account)) { - $account = $user; - - } + $recipient = token_replace($context['recipient'], $context); + $language = user_preferred_language($account); - $params = array('account' => $account, 'object' => $object, 'context' => $context); - if (isset($node)) { - $params['node'] = $node; - } + $params = array('context' => $context); if (drupal_mail('system', 'action_send_email', $recipient, $language, $params)) { watchdog('action', 'Sent email to %recipient', array('%recipient' => $recipient)); @@ -2758,39 +2713,11 @@ function system_send_email_action($object, $context) { * Implement hook_mail(). */ function system_mail($key, &$message, $params) { - $account = $params['account']; $context = $params['context']; - $variables = array( - '%site_name' => variable_get('site_name', 'Drupal'), - '%username' => $account->name, - ); - if ($context['hook'] == 'taxonomy') { - $object = $params['object']; - $vocabulary = taxonomy_vocabulary_load($object->vid); - $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, - ); - } - // Node-based variable translation is only available if we have a node. - if (isset($params['node'])) { - $node = $params['node']; - $variables += array( - '%uid' => $node->uid, - '%node_url' => url('node/' . $node->nid, array('absolute' => TRUE)), - '%node_type' => node_type_get_name($node), - '%title' => $node->title, - '%teaser' => $node->teaser, - '%body' => $node->body, - ); - } - $subject = strtr($context['subject'], $variables); - $body = strtr($context['message'], $variables); + $subject = token_replace($context['subject'], $context); + $body = token_replace($context['message'], $context); + $message['subject'] .= str_replace(array("\r", "\n"), '', $subject); $message['body'][] = drupal_html_to_text($body); } @@ -2802,7 +2729,7 @@ function system_message_action_form($context) { '#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, %term_name, %term_description, %term_id, %vocabulary_name, %vocabulary_description, %vocabulary_id. Not all variables will be available in all contexts.'), + '#description' => t('The message to be displayed to the current user. You may include placeholders like [node:title], [user:name], and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'), ); return $form; } @@ -2815,56 +2742,11 @@ function system_message_action_submit($form, $form_state) { * A configurable Drupal action. Sends a 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 'node': - // 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_type_get_name($node)), - '%title' => filter_xss($node->title), - '%teaser' => filter_xss($node->teaser), - '%body' => filter_xss($node->body), - ) - ); + if (empty($context['node'])) { + $context['node'] = $object; } - $context['message'] = strtr($context['message'], $variables); + + $context['message'] = token_replace($context['message'], $context); drupal_set_message($context['message']); } @@ -2889,7 +2771,7 @@ function system_goto_action_submit($form, $form_state) { } function system_goto_action($object, $context) { - drupal_goto($context['url']); + drupal_goto(token_replace($context['url'], $context)); } /** diff --git a/modules/system/system.test b/modules/system/system.test index ba33835e2..491fbc8fe 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -1058,3 +1058,60 @@ class QueueTestCase extends DrupalWebTestCase { return $score; } } + +/** + * Test token replacement in strings. + */ +class TokenReplaceTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Token replacement', + 'description' => 'Generates text using placeholders for dummy content to check token replacement.', + 'group' => 'System', + ); + } + + /** + * Creates a user and a node, then tests the tokens generated from them. + */ + function testTokenReplacement() { + // Create the initial objects. + $account = $this->drupalCreateUser(); + $node = $this->drupalCreateNode(array('uid' => $account->uid)); + $node->title = '<blink>Blinking Text</blink>'; + global $user; + + $source = '[node:title]'; // Title of the node we passed in + $source .= '[node:author:name]'; // Node author's name + $source .= '[node:created:since]'; // Time since the node was created + $source .= '[current-user:name]'; // Current user's name + $source .= '[user:name]'; // No user passed in, should be untouched + $source .= '[date:small]'; // Small date format of REQUEST_TIME + $source .= '[bogus:token]'; // Nonexistent token, should be untouched + + $target = check_plain($node->title); + $target .= check_plain($account->name); + $target .= format_interval(REQUEST_TIME - $node->created, 2); + $target .= check_plain($user->name); + $target .= '[user:name]'; + $target .= format_date(REQUEST_TIME, 'small'); + $target .= '[bogus:token]'; + + $result = token_replace($source, array('node' => $node)); + + // Check that the results of token_generate are sanitized properly. This does NOT + // test the cleanliness of every token -- just that the $sanitize flag is being + // passed properly through the call stack and being handled correctly by a 'known' + // token, [node:title]. + $this->assertFalse(strcmp($target, $result), t('Basic placeholder tokens replaced.')); + + $raw_tokens = array( + 'node' => array('title' => '[node:title]'), + ); + $generated = token_generate($raw_tokens, array('node' => $node)); + $this->assertFalse(strcmp($generated['[node:title]'], check_plain($node->title)), t('Token sanitized.')); + + $generated = token_generate($raw_tokens, array('node' => $node), array('sanitize' => FALSE)); + $this->assertFalse(strcmp($generated['[node:title]'], $node->title), t('Unsanitized token generated properly.')); + } +} diff --git a/modules/system/system.tokens.inc b/modules/system/system.tokens.inc new file mode 100644 index 000000000..5ab7a573b --- /dev/null +++ b/modules/system/system.tokens.inc @@ -0,0 +1,308 @@ +<?php +// $Id$ + +/** + * @file + * Builds placeholder replacement tokens system-wide data. + * + * This file handles tokens for the global 'site' token type, as well as + * 'date' and 'file' tokens. + */ + +/** + * Implement hook_token_info(). + */ +function system_token_info() { + $types['site'] = array( + 'name' => t("Site information"), + 'description' => t("Tokens for site-wide settings and other global information."), + ); + $types['date'] = array( + 'name' => t("Dates"), + 'description' => t("Tokens related to times and dates."), + ); + $types['file'] = array( + 'name' => t("Files"), + 'description' => t("Tokens related to uploaded files."), + 'needs-data' => 'file', + ); + + // Site-wide global tokens. + $site['name'] = array( + 'name' => t("Name"), + 'description' => t("The name of the site."), + ); + $site['slogan'] = array( + 'name' => t("Slogan"), + 'description' => t("The slogan of the site."), + ); + $site['mission'] = array( + 'name' => t("Mission"), + 'description' => t("The optional 'mission' of the site."), + ); + $site['mail'] = array( + 'name' => t("Email"), + 'description' => t("The administrative email address for the site."), + ); + $site['url'] = array( + 'name' => t("URL"), + 'description' => t("The URL of the site's front page."), + ); + $site['login-url'] = array( + 'name' => t("Login page"), + 'description' => t("The URL of the site's login page."), + ); + + // Date related tokens. + $date['small'] = array( + 'name' => t("Small format"), + 'description' => t("A date in 'small' format. (%date)", array('%date' => format_date(REQUEST_TIME, 'small'))), + ); + $date['medium'] = array( + 'name' => t("Medium format"), + 'description' => t("A date in 'medium' format. (%date)", array('%date' => format_date(REQUEST_TIME, 'medium'))), + ); + $date['large'] = array( + 'name' => t("Large format"), + 'description' => t("A date in 'large' format. (%date)", array('%date' => format_date(REQUEST_TIME, 'large'))), + ); + $date['custom'] = array( + 'name' => t("Custom format"), + 'description' => t("A date in a custom format. See !php-date for details.", array('!php-date' => l(t('the PHP documentation'), 'http://php.net/manual/en/function.date.php'))), + ); + $date['since'] = array( + 'name' => t("Time-since"), + 'description' => t("A data in 'time-since' format. (%date)", array('%date' => format_interval(REQUEST_TIME - 360, 2))), + ); + $date['raw'] = array( + 'name' => t("Raw timestamp"), + 'description' => t("A date in UNIX timestamp format (%date)", array('%date' => REQUEST_TIME)), + ); + + + // File related tokens. + $file['fid'] = array( + 'name' => t("File ID"), + 'description' => t("The unique ID of the uploaded file."), + ); + $file['uid'] = array( + 'name' => t("User ID"), + 'description' => t("The unique ID of the user who owns the file."), + ); + $file['nid'] = array( + 'name' => t("Node ID"), + 'description' => t("The unique ID of the node the file is attached to."), + ); + $file['name'] = array( + 'name' => t("File name"), + 'description' => t("The name of the file on disk."), + ); + $file['description'] = array( + 'name' => t("Description"), + 'description' => t("An optional human-readable description of the file."), + ); + $file['path'] = array( + 'name' => t("Path"), + 'description' => t("The location of the file on disk."), + ); + $file['mime'] = array( + 'name' => t("MIME type"), + 'description' => t("The MIME type of the file."), + ); + $file['size'] = array( + 'name' => t("File size"), + 'description' => t("The size of the file, in kilobytes."), + ); + $file['path'] = array( + 'name' => t("URL"), + 'description' => t("The web-accessible URL for the file."), + ); + $file['timestamp'] = array( + 'name' => t("Timestamp"), + 'description' => t("The date the file was most recently changed."), + 'type' => 'date', + ); + $file['node'] = array( + 'name' => t("Node"), + 'description' => t("The node the file is attached to."), + 'type' => 'date', + ); + $file['owner'] = array( + 'name' => t("Owner"), + 'description' => t("The user who originally uploaded the file."), + 'type' => 'user', + ); + + return array( + 'types' => $types, + 'tokens' => array( + 'site' => $site, + 'date' => $date, + 'file' => $file, + ), + ); +} + +/** + * Implement hook_tokens(). + */ +function system_tokens($type, $tokens, array $data = array(), array $options = array()) { + $url_options = array('absolute' => TRUE); + if (isset($language)) { + $url_options['language'] = $language; + } + $sanitize = !empty($options['sanitize']); + + $replacements = array(); + + if ($type == 'site') { + foreach ($tokens as $name => $original) { + switch ($name) { + case 'name': + $site_name = variable_get('site_name', 'Drupal'); + $replacements[$original] = $sanitize ? check_plain($site_name) : $site_name; + break; + + case 'slogan': + $slogan = variable_get('site_slogan', ''); + $replacements[$original] = $sanitize ? check_plain($slogan) : $slogan; + break; + + case 'mission': + $mission = variable_get('site_mission', ''); + $replacements[$original] = $sanitize ? filter_xss($mission) : $mission; + break; + + case 'mail': + $replacements[$original] = variable_get('site_mail', ''); + break; + + case 'url': + $replacements[$original] = url('<front>', $url_options); + break; + + case 'login-url': + $replacements[$original] = url('user', $url_options); + break; + } + } + } + + elseif ($type == 'date') { + if (empty($data['date'])) { + $date = REQUEST_TIME; + } + else { + $date = $data['date']; + } + $langcode = (isset($language) ? $language->language : NULL); + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'raw': + $replacements[$original] = filter_xss($date); + break; + + case 'small': + $replacements[$original] = format_date($date, 'small', '', NULL, $langcode); + break; + + case 'medium': + $replacements[$original] = format_date($date, 'medium', '', NULL, $langcode); + break; + + case 'large': + $replacements[$original] = format_date($date, 'large', '', NULL, $langcode); + break; + + case 'since': + $replacements[$original] = format_interval((REQUEST_TIME - $date), 2, $langcode); + break; + } + } + + if ($created_tokens = token_find_with_prefix($tokens, 'custom')) { + foreach ($created_tokens as $name => $original) { + $replacements[$original] = format_date($date, 'custom', $name, NULL, $langcode); + } + } + } + + elseif ($type == 'file' && !empty($data['file'])) { + $file = $data['file']; + + foreach ($tokens as $name => $original) { + switch ($name) { + // Basic keys and values. + case 'fid': + $replacements[$original] = $file->fid; + break; + + case 'uid': + $replacements[$original] = $file->uid; + break; + + case 'nid': + $replacements[$original] = $file->nid; + break; + + // Essential file data + case 'name': + $replacements[$original] = $sanitize ? check_plain($file->filename) : $file->filename; + break; + + case 'description': + $replacements[$original] = $sanitize ? filter_xss($file->description) : $file->description; + break; + + case 'path': + $replacements[$original] = $sanitize ? filter_xss($file->filepath) : $file->filepath; + break; + + case 'mime': + $replacements[$original] = $sanitize ? filter_xss($file->filemime) : $file->filemime; + break; + + case 'size': + $replacements[$original] = format_size($file->filesize); + break; + + case 'url': + $replacements[$original] = url(file_create_url($file->filepath), $url_options); + break; + + // These tokens are default variations on the chained tokens handled below. + case 'node': + if ($nid = $file->nid) { + $node = node_load($file->nid); + $replacements[$original] = $sanitize ? filter_xss($node->title) : $node->title; + } + break; + + case 'timestamp': + $replacements[$original] = format_date($file->timestamp, 'medium', '', NULL, (isset($language) ? $language->language : NULL)); + break; + + case 'owner': + $account = user_load($file->uid); + $replacements[$original] = $sanitize ? filter_xss($user->name) : $user->name; + break; + } + } + + if ($node_tokens = token_find_with_prefix($tokens, 'node')) { + $node = node_load($file->nid); + $replacements += token_generate('node', $node_tokens, array('node' => $node), $language, $sanitize); + } + + if ($date_tokens = token_find_with_prefix($tokens, 'timestamp')) { + $replacements += token_generate('date', $date_tokens, array('date' => $file->timestamp), $language, $sanitize); + } + + if (($owner_tokens = token_find_with_prefix($tokens, 'owner')) && $account = user_load($file->uid)) { + $replacements += token_generate('user', $owner_tokens, array('user' => $account), $language, $sanitize); + } + } + + return $replacements; +} |