summaryrefslogtreecommitdiff
path: root/modules/system
diff options
context:
space:
mode:
Diffstat (limited to 'modules/system')
-rw-r--r--modules/system/system.info1
-rw-r--r--modules/system/system.module150
-rw-r--r--modules/system/system.test57
-rw-r--r--modules/system/system.tokens.inc308
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;
+}