diff options
author | Dries Buytaert <dries@buytaert.net> | 2009-08-19 20:19:37 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2009-08-19 20:19:37 +0000 |
commit | 40003c8307ca64da0cd1ab0ee4d87f4812be8088 (patch) | |
tree | 05aa427a035a991cc82b68e42d5eb2282273bac4 | |
parent | e998857eb8a5ef4d2ebe381a57c19b1b355fe4ef (diff) | |
download | brdo-40003c8307ca64da0cd1ab0ee4d87f4812be8088.tar.gz brdo-40003c8307ca64da0cd1ab0ee4d87f4812be8088.tar.bz2 |
- Patch #113614 by eaton, fago, et al: add centralized token/placeholder subsituation to core.
-rw-r--r-- | includes/common.inc | 1 | ||||
-rw-r--r-- | includes/token.inc | 238 | ||||
-rw-r--r-- | modules/comment/comment.info | 1 | ||||
-rw-r--r-- | modules/comment/comment.tokens.inc | 248 | ||||
-rw-r--r-- | modules/node/node.info | 1 | ||||
-rw-r--r-- | modules/node/node.tokens.inc | 205 | ||||
-rw-r--r-- | modules/poll/poll.info | 1 | ||||
-rw-r--r-- | modules/poll/poll.tokens.inc | 91 | ||||
-rw-r--r-- | modules/statistics/statistics.info | 1 | ||||
-rw-r--r-- | modules/statistics/statistics.tokens.inc | 63 | ||||
-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 | ||||
-rw-r--r-- | modules/taxonomy/taxonomy.info | 1 | ||||
-rw-r--r-- | modules/taxonomy/taxonomy.tokens.inc | 189 | ||||
-rw-r--r-- | modules/upload/upload.info | 1 | ||||
-rw-r--r-- | modules/upload/upload.tokens.inc | 45 | ||||
-rw-r--r-- | modules/user/user.info | 1 | ||||
-rw-r--r-- | modules/user/user.tokens.inc | 129 |
20 files changed, 1598 insertions, 134 deletions
diff --git a/includes/common.inc b/includes/common.inc index f6e64cf47..4e701de9e 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -3549,6 +3549,7 @@ function _drupal_bootstrap_full() { require_once DRUPAL_ROOT . '/includes/mail.inc'; require_once DRUPAL_ROOT . '/includes/actions.inc'; require_once DRUPAL_ROOT . '/includes/ajax.inc'; + require_once DRUPAL_ROOT . '/includes/token.inc'; // Set the Drupal custom error handler. set_error_handler('_drupal_error_handler'); set_exception_handler('_drupal_exception_handler'); diff --git a/includes/token.inc b/includes/token.inc new file mode 100644 index 000000000..7f786121d --- /dev/null +++ b/includes/token.inc @@ -0,0 +1,238 @@ +<?php +// $Id$ + +/** + * @file + * Drupal placeholder/token replacement system. + * + * Provides a set of extensible API functions for replacing placeholders in text + * with meaningful values. + * + * For example: When configuring automated emails, an administrator enters standard + * text for the email. Variables like the title of a node and the date the email + * was sent can be entered as placeholders like [node:title] and [date:short]. + * When a Drupal module prepares to send the email, it can call the token_replace() + * function, passing in the text. The token system will scan the text for placeholder + * tokens, give other modules an opportunity to replace them with meaningful text, + * then return the final product to the original module. + * + * Tokens follow the form: [$type:$name], where $type is a general class of + * tokens like 'node', 'user', or 'comment' and $name is the name of a given + * placeholder. For example, [node:title]. + * + * In addition to raw text containing placeholders, modules may pass in an array + * of objects to be used when performing the replacement. The objects should be + * keyed by the token type they correspond to. For example: + * + * @code + * // Load a node and a user, then replace tokens in the text. + * $text = 'On [date:short], [user:name] read [node:title].'; + * $node = node_load(1); + * $user = user_load(1); + * + * // [date:...] tokens use the current date automatically. + * $data = array('node' => $node, 'user' => $user); + * return token_replace($text, $data); + * @endcode + * + * Some tokens may be chained in the form of [$type:$pointer:$name], where $type + * is a normal token type, $pointer is a reference to another token type, and + * $name is the name of a given placeholder. For example, [node:author:mail]. In + * that example, 'author' is a pointer to the 'user' account that created the node, + * and 'mail' is a placeholder available for any 'user'. + * + * @see token_replace() + * @see hook_tokens() + * @see hook_token_info() + */ + +/** + * Replace all tokens in a given string with appropriate values. + * + * @param $text + * A string potentially containing replacable tokens. + * @param $data + * (optional) An array of keyed objects. For simple replacement scenarios + * 'node', 'user', and others are common keys, with an accompanying node or + * user object being the value. Some token types, like 'site', do not require + * any explicit information from $data and can be replaced even if it is empty. + * @param $options + * (optional) A keyed array of settings and flags to control the token + * replacement process. Supported options are: + * - language: A language object to be used when generating locale-sensitive + * tokens. + * - callback: A callback function that will be used to post-process the array + * of token replacements after they are generated. For example, a module using + * tokens in a text-only email might provide a callback to strip HTML + * entities from token values before they are inserted into the final text. + * - clear: A boolean flag indicating that tokens should be removed from the + * final text if no replacement value can be generated. + * - sanitize: A boolean flag indicating that tokens should be sanitized for + * display to a web browser. Defaults to TRUE. Developers who set this option + * to FALSE assume responsibility for running filter_xss(), check_plain() or + * other appropriate scrubbing functions before displaying data to users. + * @return + * Text with tokens replaced. + */ +function token_replace($text, array $data = array(), array $options = array()) { + $replacements = array(); + foreach (token_scan($text) as $type => $tokens) { + $replacements += token_generate($type, $tokens, $data, $options); + } + + // Optionally alter the list of replacement values. + if (!empty($options['callback']) && drupal_function_exists($options['callback'])) { + $function = $options['callback']; + $function($replacements, $data, $options); + } + + $tokens = array_keys($replacements); + $values = array_values($replacements); + + return str_replace($tokens, $values, $text); +} + +/** + * Build a list of all token-like patterns that appear in the text. + * + * @param $text + * The text to be scanned for possible tokens. + * @return + * An associative array of discovered tokens, grouped by type. + */ +function token_scan($text) { + // Matches tokens with the following pattern: [$type:$token] + // $type and $token may not contain white spaces. + preg_match_all('/\[([^\s\]:]*):([^\s\]]*)\]/', $text, $matches); + + $types = $matches[1]; + $tokens = $matches[2]; + + // Iterate through the matches, building an associative array containing + // $tokens grouped by $types, pointing to the version of the token found in + // the source text. For example, $results['node']['title'] = '[node:title]'; + $results = array(); + for ($i = 0; $i < count($tokens); $i++) { + $results[$types[$i]][$tokens[$i]] = $matches[0][$i]; + } + + return $results; +} + +/** + * Generate replacement values for a list of tokens. + * + * @param $type + * The type of token being replaced. 'node', 'user', and 'date' are common. + * @param $tokens + * An array of tokens to be replaced, keyed by the literal text of the token + * as it appeared in the source text. + * @param $data + * (optional) An array of keyed objects. For simple replacement scenarios + * 'node', 'user', and others are common keys, with an accompanying node or + * user object being the value. Some token types, like 'site', do not require + * any explicit information from $data and can be replaced even if it is empty. + * @param $options + * (optional) A keyed array of settings and flags to control the token + * replacement process. Supported options are: + * - 'language' A language object to be used when generating locale-sensitive + * tokens. + * - 'callback' A callback function that will be used to post-process the array + * of token replacements after they are generated. Can be used when modules + * require special formatting of token text, for example URL encoding or + * truncation to a specific length. + * - 'sanitize' A boolean flag indicating that tokens should be sanitized for + * display to a web browser. Developers who set this option to FALSE assume + * responsibility for running filter_xss(), check_plain() or other + * appropriate scrubbing functions before displaying data to users. + * @return + * An associative array of replacement values, keyed by the original 'raw' + * tokens that were found in the source text. For example: + * $results['[node:title]'] = 'My new node'; + */ +function token_generate($type, array $tokens, array $data = array(), array $options = array()) { + $results = array(); + $options += array('sanitize' => TRUE); + + foreach (module_implements('tokens') as $module) { + $function = $module . '_tokens'; + if (drupal_function_exists($function)) { + $result = $function($type, $tokens, $data, $options); + foreach ($result as $original => $replacement) { + $results[$original] = $replacement; + } + } + } + + return $results; +} + +/** + * Given a list of tokens, return those that begin with a specific prefix. + * + * Used to extract a group of 'chained' tokens (such as [node:author:name]) from + * the full list of tokens found in text. For example: + * @code + * $data = array( + * 'author:name' => '[node:author:name]', + * 'title' => '[node:title]', + * 'created' => '[node:author:name]', + * ); + * $results = token_find_with_prefix($data, 'author'); + * $results == array('name' => '[node:author:name]'); + * @endcode + * + * @param $tokens + * A keyed array of tokens, and their original raw form in the source text. + * @param $prefix + * A textual string to be matched at the beginning of the token. + * @param $delimiter + * An optional string containing the character that separates the prefix from + * the rest of the token. Defaults to ':'. + * @return + * An associative array of discovered tokens, with the prefix and delimiter + * stripped from the key. + */ +function token_find_with_prefix(array $tokens, $prefix, $delimiter = ':') { + $results = array(); + foreach ($tokens as $token => $raw) { + $parts = split($delimiter, $token, 2); + if (count($parts) == 2 && $parts[0] == $prefix) { + $results[$parts[1]] = $raw; + } + } + return $results; +} + +/** + * Returns metadata describing supported tokens. + * + * The metadata array contains token type, name, and description data as well as + * an optional pointer indicating that the token chains to another set of tokens. + * For example: + * @code + * $data['types']['node'] = array( + * 'name' => t('Nodes'), + * 'description' => t('Tokens related to node objects.'), + * ); + * $data['tokens']['node']['title'] = array( + * 'name' => t('Title'), + * 'description' => t('The title of the current node.'), + * ); + * $data['tokens']['node']['author'] = array( + * 'name' => t('Author'), + * 'description' => t('The author of the current node.'), + * 'type' => 'user', + * ); + * @endcode + * @return + * An associative array of token information, grouped by token type. + */ +function token_info() { + $data = &drupal_static(__FUNCTION__); + if (!isset($data)) { + $data = module_invoke_all('token_info'); + drupal_alter('token_info', $data); + } + return $data; +} diff --git a/modules/comment/comment.info b/modules/comment/comment.info index 71a62e3ed..5b6a48acf 100644 --- a/modules/comment/comment.info +++ b/modules/comment/comment.info @@ -10,3 +10,4 @@ files[] = comment.admin.inc files[] = comment.pages.inc files[] = comment.install files[] = comment.test +files[] = comment.tokens.inc diff --git a/modules/comment/comment.tokens.inc b/modules/comment/comment.tokens.inc new file mode 100644 index 000000000..09b89116b --- /dev/null +++ b/modules/comment/comment.tokens.inc @@ -0,0 +1,248 @@ +<?php +// $Id$ + +/** + * @file + * Builds placeholder replacement tokens for comment-related data. + */ + +/** + * Implement hook_token_info(). + */ +function comment_token_info() { + $type = array( + 'name' => t('Comments'), + 'description' => t('Tokens for comments posted on the site.'), + 'needs-data' => 'comment', + ); + + // Comment-related tokens for nodes + $node['comment-count'] = array( + 'name' => t("Comment count"), + 'description' => t("The number of comments posted on a node."), + ); + $node['comment-count-new'] = array( + 'name' => t("New comment count"), + 'description' => t("The number of comments posted on a node since the reader last viewed it."), + ); + + // Core comment tokens + $comment['cid'] = array( + 'name' => t("Comment ID"), + 'description' => t("The unique ID of the comment."), + ); + $comment['pid'] = array( + 'name' => t("Parent ID"), + 'description' => t("The unique ID of the comment's parent, if comment threading is active."), + ); + $comment['nid'] = array( + 'name' => t("Node ID"), + 'description' => t("The unique ID of the node the comment was posted to."), + ); + $comment['uid'] = array( + 'name' => t("User ID"), + 'description' => t("The unique ID of the user who posted the comment."), + ); + $comment['hostname'] = array( + 'name' => t("IP Address"), + 'description' => t("The IP address of the computer the comment was posted from."), + ); + $comment['name'] = array( + 'name' => t("Name"), + 'description' => t("The name left by the comment author."), + ); + $comment['mail'] = array( + 'name' => t("Email address"), + 'description' => t("The email address left by the comment author."), + ); + $comment['homepage'] = array( + 'name' => t("Home page"), + 'description' => t("The home page URL left by the comment author."), + ); + $comment['title'] = array( + 'name' => t("Title"), + 'description' => t("The title of the comment."), + ); + $comment['body'] = array( + 'name' => t("Content"), + 'description' => t("The formatted content of the comment itself."), + ); + $comment['url'] = array( + 'name' => t("URL"), + 'description' => t("The URL of the comment."), + ); + $comment['edit-url'] = array( + 'name' => t("Edit URL"), + 'description' => t("The URL of the comment's edit page."), + ); + + // Chained tokens for comments + $comment['created'] = array( + 'name' => t("Date created"), + 'description' => t("The date the comment was posted."), + 'type' => 'date', + ); + $comment['parent'] = array( + 'name' => t("Parent"), + 'description' => t("The comment's parent, if comment threading is active."), + 'type' => 'comment', + ); + $comment['node'] = array( + 'name' => t("Node"), + 'description' => t("The node the comment was posted to."), + 'type' => 'node', + ); + $comment['author'] = array( + 'name' => t("Author"), + 'description' => t("The author of the comment, if they were logged in."), + 'type' => 'user', + ); + + return array( + 'types' => array('comment' => $type), + 'tokens' => array( + 'node' => $node, + 'comment' => $comment, + ), + ); +} + +/** + * Implement hook_tokens(). + */ +function comment_tokens($type, $tokens, array $data = array(), array $options = array()) { + $url_options = array('absolute' => TRUE); + if (isset($options['language'])) { + $url_options['language'] = $language; + $language_code = $language->language; + } + else { + $language_code = NULL; + } + $sanitize = !empty($options['sanitize']); + + $replacements = array(); + + if ($type == 'comment' && !empty($data['comment'])) { + $comment = $data['comment']; + + foreach ($tokens as $name => $original) { + switch ($name) { + // Simple key values on the comment. + case 'cid': + $replacements[$original] = $comment->cid; + break; + + case 'nid': + $replacements[$original] = $comment->nid; + break; + + case 'uid': + $replacements[$original] = $comment->uid; + break; + + case 'pid': + $replacements[$original] = $comment->pid; + break; + + // Poster identity information for comments + case 'hostname': + $replacements[$original] = $sanitize ? check_plain($comment->hostname) : $comment->hostname; + break; + + case 'name': + $name = ($comment->uid == 0) ? variable_get('anonymous', t('Anonymous')) : $comment->name; + $replacements[$original] = $sanitize ? filter_xss($name) : $name; + break; + + case 'mail': + if ($comment->uid != 0) { + $account = user_load($comment->uid); + $mail = $account->mail; + } + else { + $mail = $comment->mail; + } + $replacements[$original] = $sanitize ? check_plain($mail) : $mail; + break; + + case 'homepage': + $replacements[$original] = $sanitize ? filter_xss_bad_protocol($comment->homepage) : $comment->homepage; + break; + + case 'title': + $replacements[$original] = $sanitize ? filter_xss($comment->subject) : $comment->subject; + break; + + case 'body': + $replacements[$original] = $sanitize ? check_markup($comment->comment, $comment->format) : $replacements[$original] = $comment->comment; + break; + + // Comment related URLs. + case 'url': + $replacements[$original] = url('comment/' . $comment->cid, array('absolute' => TRUE, 'fragment' => 'comment-' . $comment->cid)); + break; + + case 'edit-url': + $replacements[$original] = url('comment/edit/' . $comment->cid, array('absolute' => TRUE)); + break; + + // Default values for the chained tokens handled below. + case 'author': + $replacements[$original] = $sanitize ? filter_xss($comment->name) : $comment->name; + break; + + case 'parent': + if (!empty($comment->pid)) { + $parent = comment_load($comment->pid); + $replacements[$original] = $sanitize ? filter_xss($parent->subject) : $parent->subject; + } + break; + + case 'created': + $replacements[$original] = format_date($comment->timestamp, 'medium', '', NULL, $language_code); + break; + + case 'node': + $node = node_load($comment->nid); + $replacements[$original] = $sanitize ? filter_xss($node->title) : $node->title; + break; + } + } + + // Chained token relationships. + if ($node_tokens = token_find_with_prefix($tokens, 'node')) { + $node = node_load($comment->nid); + $replacements += token_generate('node', $node_tokens, array('node' => $node), $options); + } + + if ($date_tokens = token_find_with_prefix($tokens, 'created')) { + $replacements += token_generate('date', $date_tokens, array('date' => $comment->timestamp), $options); + } + + if (($parent_tokens = token_find_with_prefix($tokens, 'parent')) && $parent = comment_load($comment->pid)) { + $replacements += token_generate('comment', $parent_tokens, array('comment' => $parent), $options); + } + + if (($author_tokens = token_find_with_prefix($tokens, 'author')) && $account = user_load($comment->uid)) { + $replacements += token_generate('user', $author_tokens, array('user' => $account), $options); + } + } + elseif ($type == 'node' & !empty($data['node'])) { + $node = $data['node']; + + foreach ($tokens as $name => $original) { + switch($name) { + case 'comment-count': + $replacements[$original] = $node->comment_count; + break; + + case 'comment-count-new': + $replacements[$original] = comment_num_new($node->nid); + break; + } + } + } + + return $replacements; +} diff --git a/modules/node/node.info b/modules/node/node.info index 2f62a9901..300213411 100644 --- a/modules/node/node.info +++ b/modules/node/node.info @@ -10,4 +10,5 @@ files[] = node.admin.inc files[] = node.pages.inc files[] = node.install files[] = node.test +files[] = node.tokens.inc required = TRUE diff --git a/modules/node/node.tokens.inc b/modules/node/node.tokens.inc new file mode 100644 index 000000000..915ac4100 --- /dev/null +++ b/modules/node/node.tokens.inc @@ -0,0 +1,205 @@ +<?php +// $Id$ + +/** + * @file + * Builds placeholder replacement tokens for node-related data. + */ + + + +/** + * Implement hook_token_info(). + */ +function node_token_info() { + $type = array( + 'name' => t('Nodes'), + 'description' => t('Tokens related to individual nodes.'), + 'needs-data' => 'node', + ); + + // Core tokens for nodes. + $node['nid'] = array( + 'name' => t("Node ID"), + 'description' => t("The unique ID of the node."), + ); + $node['vid'] = array( + 'name' => t("Revision ID"), + 'description' => t("The unique ID of the node's latest revision."), + ); + $node['tnid'] = array( + 'name' => t("Translation set ID"), + 'description' => t("The unique ID of the original-language version of this node, if one exists."), + ); + $node['uid'] = array( + 'name' => t("User ID"), + 'description' => t("The unique ID of the user who posted the node."), + ); + $node['type'] = array( + 'name' => t("Content type"), + 'description' => t("The type of the node."), + ); + $node['type-name'] = array( + 'name' => t("Content type name"), + 'description' => t("The human-readable name of the node type."), + ); + $node['title'] = array( + 'name' => t("Title"), + 'description' => t("The title of the node."), + ); + $node['body'] = array( + 'name' => t("Body"), + 'description' => t("The main body text of the node."), + ); + $node['summary'] = array( + 'name' => t("Summary"), + 'description' => t("The summary of the node's main body text."), + ); + $node['language'] = array( + 'name' => t("Language"), + 'description' => t("The language the node is written in."), + ); + $node['url'] = array( + 'name' => t("URL"), + 'description' => t("The URL of the node."), + ); + $node['edit-url'] = array( + 'name' => t("Edit URL"), + 'description' => t("The URL of the node's edit page."), + ); + + // Chained tokens for nodes. + $node['created'] = array( + 'name' => t("Date created"), + 'description' => t("The date the node was posted."), + 'type' => 'date', + ); + $node['changed'] = array( + 'name' => t("Date changed"), + 'description' => t("The date the node was most recently updated."), + 'type' => 'date', + ); + $node['author'] = array( + 'name' => t("Author"), + 'description' => t("The author of the node."), + 'type' => 'user', + ); + + return array( + 'types' => array('node' => $type), + 'tokens' => array('node' => $node), + ); +} + +/** + * Implement hook_tokens(). + */ +function node_tokens($type, $tokens, array $data = array(), array $options = array()) { + $url_options = array('absolute' => TRUE); + if (isset($options['language'])) { + $url_options['language'] = $language; + $language_code = $language->language; + } + else { + $language_code = NULL; + } + $sanitize = !empty($options['sanitize']); + + $replacements = array(); + + if ($type == 'node' && !empty($data['node'])) { + $node = $data['node']; + + foreach ($tokens as $name => $original) { + switch ($name) { + // Simple key values on the node. + case 'nid': + $replacements[$original] = $node->nid; + break; + + case 'vid': + $replacements[$original] = $node->vid; + break; + + case 'tnid': + $replacements[$original] = $node->tnid; + break; + + case 'uid': + $replacements[$original] = $node->uid; + break; + + case 'name': + $replacements[$original] = $sanitize ? check_plain($node->name) : $node->name; + break; + + case 'title': + $replacements[$original] = $sanitize ? check_plain($node->title) : $node->title; + break; + + case 'body': + if (!empty($node->body)) { + $replacements[$original] = $sanitize ? $node->body[0]['safe'] : $node->body[0]['value']; + } + break; + + case 'summary': + if (!empty($node->body)) { + $replacements[$original] = $sanitize ? $node->body[0]['safe_summary'] : $node->body[0]['summary']; + } + break; + + case 'type': + $replacements[$original] = $sanitize ? check_plain($node->type) : $node->type; + break; + + case 'type-name': + $type_name = node_get_types('name', $node->type); + $replacements[$original] = $sanitize ? check_plain($type_name) : $type_name; + break; + + case 'language': + $replacements[$original] = $sanitize ? check_plain($node->language) : $node->language; + break; + + case 'url': + $replacements[$original] = url('node/' . $node->nid, array('absolute' => TRUE)); + break; + + case 'edit-url': + $replacements[$original] = url('node/' . $node->nid . '/edit', array('absolute' => TRUE)); + break; + + // Default values for the chained tokens handled below. + case 'author': + $name = ($node->uid == 0) ? variable_get('anonymous', t('Anonymous')) : $node->name; + $replacements[$original] = $sanitize ? filter_xss($name) : $name; + break; + + case 'created': + $replacements[$original] = format_date($node->created, 'medium', '', NULL, $language_code); + break; + + case 'changed': + $replacements[$original] = format_date($node->changed, 'medium', '', NULL, $language_code); + break; + } + dsm('node'); + } + + if ($author_tokens = token_find_with_prefix($tokens, 'author')) { + $author = user_load($node->uid); + $replacements += token_generate('user', $author_tokens, array('user' => $author), $options); + } + + if ($created_tokens = token_find_with_prefix($tokens, 'created')) { + $replacements += token_generate('date', $created_tokens, array('date' => $node->created), $options); + } + + if ($changed_tokens = token_find_with_prefix($tokens, 'changed')) { + $replacements += token_generate('date', $changed_tokens, array('date' => $node->changed), $options); + } + } + + return $replacements; +} diff --git a/modules/poll/poll.info b/modules/poll/poll.info index d59cc9b93..fd2f3149b 100644 --- a/modules/poll/poll.info +++ b/modules/poll/poll.info @@ -8,3 +8,4 @@ files[] = poll.module files[] = poll.pages.inc files[] = poll.install files[] = poll.test +files[] = poll.tokens.inc diff --git a/modules/poll/poll.tokens.inc b/modules/poll/poll.tokens.inc new file mode 100644 index 000000000..9fe6857ad --- /dev/null +++ b/modules/poll/poll.tokens.inc @@ -0,0 +1,91 @@ +<?php +// $Id$ + +/** + * @file + * Builds placeholder replacement tokens for values specific to Poll nodes. + */ + +/** + * Implement hook_token_info(). + */ +function poll_token_info() { + $node['poll-votes'] = array( + 'name' => t("Poll votes"), + 'description' => t("The number of votes that have been cast on a poll node."), + ); + $node['poll-winner'] = array( + 'name' => t("Poll winner"), + 'description' => t("The winning poll answer."), + ); + $node['poll-winner-votes'] = array( + 'name' => t("Poll winner votes"), + 'description' => t("The number of votes received by the winning poll answer."), + ); + $node['poll-winner-percent'] = array( + 'name' => t("Poll winner percent"), + 'description' => t("The percentage of votes received by the winning poll answer."), + ); + $node['poll-duration'] = array( + 'name' => t("Poll duration"), + 'description' => t("The length of time the poll node is set to run."), + ); + + return array( + 'tokens' => array('node' => $node), + ); +} + +/** + * Implement hook_tokens(). + */ +function poll_tokens($type, $tokens, array $data = array(), array $options = array()) { + $url_options = array('absolute' => TRUE); + $sanitize = !empty($options['sanitize']); + + if ($type == 'node' && !empty($data['node']) && $data['node']->type == 'poll') { + $node = $data['node']; + + $total_votes = 0; + $highest_votes = 0; + foreach ($node->choice as $choice) { + if ($choice['chvotes'] > $highest_votes) { + $winner = $choice; + $highest_votes = $choice['chvotes']; + } + $total_votes = $total_votes + $choice['chvotes']; + } + foreach ($tokens as $name => $original) { + switch ($name) { + case 'poll-votes': + $replacements[$original] = $total_votes; + break; + + case 'poll-winner': + if (isset($winner)) { + $replacements[$original] = $sanitize ? filter_xss($winner['chtext']) : $winner['chtext']; + } + break; + + case 'poll-winner-votes': + if (isset($winner)) { + $replacements[$original] = $winner['chvotes']; + } + break; + + case 'poll-winner-percent': + if (isset($winner)) { + $percent = ($winner['chvotes'] / $total_votes) * 100; + $replacements[$original] = number_format($percent, 0); + } + break; + + case 'poll-duration': + $replacements[$original] = format_interval($node->runtime, 1, $language_code); + break; + } + } + } + + return $replacements; +} diff --git a/modules/statistics/statistics.info b/modules/statistics/statistics.info index a3a509124..83b4351c3 100644 --- a/modules/statistics/statistics.info +++ b/modules/statistics/statistics.info @@ -9,3 +9,4 @@ files[] = statistics.admin.inc files[] = statistics.pages.inc files[] = statistics.install files[] = statistics.test +files[] = statistics.tokens.inc diff --git a/modules/statistics/statistics.tokens.inc b/modules/statistics/statistics.tokens.inc new file mode 100644 index 000000000..2a0e59888 --- /dev/null +++ b/modules/statistics/statistics.tokens.inc @@ -0,0 +1,63 @@ +<?php +// $Id$ + +/** + * @file + * Builds placeholder replacement tokens for node visitor statistics. + */ + +/** + * Implement hook_token_info(). + */ +function statistics_token_info() { + $node['views'] = array( + 'name' => t("Number of views"), + 'description' => t("The number of visitors who have read the node."), + ); + $node['day-views'] = array( + 'name' => t("Views today"), + 'description' => t("The number of visitors who have read the node today."), + ); + $node['last-view'] = array( + 'name' => t("Last view"), + 'description' => t("The date on which a visitor last read the node."), + 'type' => 'date', + ); + + return array( + 'tokens' => array('node' => $node), + ); +} + +/** + * Implement hook_tokens(). + */ +function statistics_tokens($type, $tokens, array $data = array(), array $options = array()) { + $url_options = array('absolute' => TRUE); + + if ($type == 'node' & !empty($data['node'])) { + $node = $data['node']; + + foreach ($tokens as $name => $original) { + if ($name == 'views') { + $statistics = statistics_get($node->nid); + $replacements[$original] = $statistics['totalviews']; + } + elseif ($name == 'views-today') { + $statistics = statistics_get($node->nid); + $replacements[$original] = $statistics['dayviews']; + } + elseif ($name == 'last-view') { + $statistics = statistics_get($node->nid); + $replacements[$original] = format_date($statistics['timestamp']); + } + } + + if ($created_tokens = token_find_with_prefix($tokens, 'last-view')) { + $statistics = statistics_get($node->nid); + $replacements += token_generate('date', $created_tokens, array('date' => $statistics['timestamp']), $options); + } + } + + return $replacements; +} 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; +} diff --git a/modules/taxonomy/taxonomy.info b/modules/taxonomy/taxonomy.info index 35dfa1480..0bec863d2 100644 --- a/modules/taxonomy/taxonomy.info +++ b/modules/taxonomy/taxonomy.info @@ -9,3 +9,4 @@ files[] = taxonomy.admin.inc files[] = taxonomy.pages.inc files[] = taxonomy.install files[] = taxonomy.test +files[] = taxonomy.tokens.inc diff --git a/modules/taxonomy/taxonomy.tokens.inc b/modules/taxonomy/taxonomy.tokens.inc new file mode 100644 index 000000000..fc321700f --- /dev/null +++ b/modules/taxonomy/taxonomy.tokens.inc @@ -0,0 +1,189 @@ +<?php +// $Id$ + +/** + * @file + * Builds placeholder replacement tokens for taxonomy terms and vocabularies. + */ + +/** + * Implement hook_token_info(). + */ +function taxonomy_token_info() { + $types['term'] = array( + 'name' => t("Taxonomy terms"), + 'description' => t("Tokens related to taxonomy terms."), + 'needs-data' => 'term', + ); + $types['vocabulary'] = array( + 'name' => t("Vocabularies"), + 'description' => t("Tokens related to taxonomy vocabularies."), + 'needs-data' => 'vocabulary', + ); + + // Taxonomy term related variables. + $term['tid'] = array( + 'name' => t("Term ID"), + 'description' => t("The unique ID of the taxonomy term."), + ); + $term['vid'] = array( + 'name' => t("Vocabulary ID"), + 'description' => t("The unique ID of the vocabulary the term belongs to."), + ); + $term['name'] = array( + 'name' => t("Name"), + 'description' => t("The name of the taxonomy term."), + ); + $term['description'] = array( + 'name' => t("Description"), + 'description' => t("The optional description of the taxonomy term."), + ); + $term['node-count'] = array( + 'name' => t("Node count"), + 'description' => t("The number of nodes tagged with the taxonomy term."), + ); + $term['url'] = array( + 'name' => t("URL"), + 'description' => t("The URL of the taxonomy term."), + ); + + // Taxonomy vocabulary related variables. + $vocabulary['vid'] = array( + 'name' => t("Vocabulary ID"), + 'description' => t("The unique ID of the taxonomy vocabulary."), + ); + $vocabulary['name'] = array( + 'name' => t("Name"), + 'description' => t("The name of the taxonomy vocabulary."), + ); + $vocabulary['description'] = array( + 'name' => t("Description"), + 'description' => t("The optional description of the taxonomy vocabulary."), + ); + $vocabulary['node-count'] = array( + 'name' => t("Node count"), + 'description' => t("The number of nodes tagged with terms belonging to the taxonomy vocabulary."), + ); + $vocabulary['term-count'] = array( + 'name' => t("Node count"), + 'description' => t("The number of terms belonging to the taxonomy vocabulary."), + ); + + // Chained tokens for taxonomies + $term['vocabulary'] = array( + 'name' => t("Vocabulary"), + 'description' => t("The vocabulary the taxonomy term belongs to."), + 'type' => 'vocabulary', + ); + $term['parent'] = array( + 'name' => t("Parent term"), + 'description' => t("The parent term of the taxonomy term, if one exists."), + 'type' => 'term', + ); + + return array( + 'types' => $types, + 'tokens' => array( + 'term' => $term, + 'vocabulary' => $vocabulary, + ), + ); +} + +/** + * Implement hook_tokens(). + */ +function taxonomy_tokens($type, $tokens, array $data = array(), array $options = array()) { + $replacements = array(); + $sanitize = !empty($options['sanitize']); + + if ($type == 'term' && !empty($data['term'])) { + $term = $data['term']; + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'tid': + $replacements[$original] = $term->tid; + break; + + case 'vid': + $replacements[$original] = $term->vid; + break; + + case 'name': + $replacements[$original] = $sanitize ? check_plain($term->name) : $term->name; + break; + + case 'description': + $replacements[$original] = $sanitize ? filter_xss($term->description) : $term->description; + break; + + case 'url': + $replacements[$original] = url(taxonomy_term_path($term), array('absolute' => TRUE)); + break; + + case 'node-count': + $sql = "SELECT COUNT (1) FROM {taxonomy_term_node} tn WHERE tn.tid = :tid"; + $count = db_result(db_query($sql, array(':tid' => $term->tid))); + $replacements[$original] = $count; + break; + + case 'vocabulary': + $vocabulary = taxonomy_vocabulary_load($term->vid); + $replacements[$original] = check_plain($vocabulary->name); + break; + + case 'parent': + $parents = taxonomy_get_parents($term->tid); + $parent = array_pop($parents); + $replacements[$original] = check_plain($parent->name); + break; + } + } + + if ($vocabulary_tokens = token_find_with_prefix($tokens, 'vocabulary')) { + $vocabulary = taxonomy_vocabulary_load($term->vid); + $replacements += token_generate('vocabulary', $vocabulary_tokens, array('vocabulary' => $vocabulary), $options); + } + + if ($vocabulary_tokens = token_find_with_prefix($tokens, 'parent')) { + $parents = taxonomy_get_parents($term->tid); + $parent = array_pop($parents); + $replacements += token_generate('term', $vocabulary_tokens, array('term' => $parent), $options); + } + } + + elseif ($type == 'vocabulary' && !empty($data['vocabulary'])) { + $vocabulary = $data['vocabulary']; + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'vid': + $replacements[$original] = $vocabulary->vid; + break; + + case 'name': + $replacements[$original] = $sanitize ? check_plain($vocabulary->name) : $vocabulary->name; + break; + + case 'description': + $replacements[$original] = $sanitize ? filter_xss($vocabulary->description) : $vocabulary->description; + break; + + case 'term-count': + $sql = "SELECT COUNT (1) FROM {taxonomy_term_data} td WHERE td.vid = :vid"; + $count = db_result(db_query($sql, array(':vid' => $vocabulary->vid))); + $replacements[$original] = $count; + break; + + case 'node-count': + $sql = "SELECT COUNT (1) FROM {taxonomy_term_node} tn LEFT JOIN {taxonomy_term_data} td ON tn.tid = td.tid WHERE td.vid = :vid"; + $count = db_result(db_query($sql, array(':vid' => $vocabulary->vid))); + $replacements[$original] = $count; + break; + } + } + } + + return $replacements; +} diff --git a/modules/upload/upload.info b/modules/upload/upload.info index d5039f521..a3253433c 100644 --- a/modules/upload/upload.info +++ b/modules/upload/upload.info @@ -8,3 +8,4 @@ files[] = upload.module files[] = upload.admin.inc files[] = upload.install files[] = upload.test +files[] = upload.tokens.inc diff --git a/modules/upload/upload.tokens.inc b/modules/upload/upload.tokens.inc new file mode 100644 index 000000000..92c18367a --- /dev/null +++ b/modules/upload/upload.tokens.inc @@ -0,0 +1,45 @@ +<?php +// $Id$ + +/** + * @file + * Builds placeholder replacement tokens for uploaded files attached to nodes. + */ + +/** + * Implement hook_token_info(). + */ +function upload_token_info() { + $results['tokens']['node'] = array( + 'upload' => array( + 'name' => t('File attachment'), + 'description' => t('The first file attached to a node, if one exists.'), + 'type' => 'file', + ) + ); + return $results; +} + +/** + * Implement hook_tokens(). + */ +function upload_tokens($type, $tokens, array $data = array(), array $options = array()) { + $replacements = array(); + + if ($type == 'node' && !empty($data['node'])) { + $node = $data['node']; + + foreach ($tokens as $name => $original) { + if ($name == 'upload') { + $upload = array_shift($node->files); + $replacements[$original] = file_create_url($upload->filepath); + } + } + + if (($upload_tokens = token_find_with_prefix($tokens, 'upload')) && !empty($node->files) && $upload = array_shift($node->files)) { + $replacements += token_generate('file', $upload_tokens, array('file' => $upload), $options); + } + } + + return $replacements; +} diff --git a/modules/user/user.info b/modules/user/user.info index d4c24fa77..cd916a873 100644 --- a/modules/user/user.info +++ b/modules/user/user.info @@ -9,4 +9,5 @@ files[] = user.admin.inc files[] = user.pages.inc files[] = user.install files[] = user.test +files[] = user.tokens.inc required = TRUE diff --git a/modules/user/user.tokens.inc b/modules/user/user.tokens.inc new file mode 100644 index 000000000..11048e72c --- /dev/null +++ b/modules/user/user.tokens.inc @@ -0,0 +1,129 @@ +<?php +// $Id$ + +/** + * @file + * Builds placeholder replacement tokens for user-related data. + */ + +/** + * Implement hook_token_info(). + */ +function user_token_info() { + $types['user'] = array( + 'name' => t('Users'), + 'description' => t('Tokens related to individual user accounts.'), + 'needs-data' => 'user', + ); + $types['current-user'] = array( + 'name' => t('Current user'), + 'description' => t('Tokens related to the currently logged in user.'), + 'type' => 'user', + ); + + $user['uid'] = array( + 'name' => t('User ID'), + 'description' => t("The unique ID of the user account."), + ); + $user['name'] = array( + 'name' => t("Name"), + 'description' => t("The login name of the user account."), + ); + $user['mail'] = array( + 'name' => t("Email"), + 'description' => t("The email address of the user account."), + ); + $user['url'] = array( + 'name' => t("URL"), + 'description' => t("The URL of the account profile page."), + ); + $user['edit-url'] = array( + 'name' => t("Edit URL"), + 'description' => t("The url of the account edit page."), + ); + $user['last-login'] = array( + 'name' => t("Last login"), + 'description' => t("The date the user last logged in to the site."), + 'type' => 'date', + ); + $user['created'] = array( + 'name' => t("Created"), + 'description' => t("The date the user account was created."), + 'type' => 'date', + ); + + return array( + 'types' => array('user' => $types), + 'tokens' => array('user' => $user), + ); +} + +/** + * Implement hook_tokens(). + */ +function user_tokens($type, $tokens, array $data = array(), array $options = array()) { + global $user; + $url_options = array('absolute' => TRUE); + if (isset($options['language'])) { + $url_options['language'] = $language; + $language_code = $language->language; + } + else { + $language_code = NULL; + } + $sanitize = !empty($options['sanitize']); + + $replacements = array(); + + if ($type == 'user' && !empty($data['user'])) { + $account = $data['user']; + foreach ($tokens as $name => $original) { + switch ($name) { + // Basic user account information. + case 'uid': + $replacements[$original] = $account->uid; + break; + + case 'name': + $name = ($account->uid == 0) ? variable_get('anonymous', t('Anonymous')) : $account->name; + $replacements[$original] = $sanitize ? filter_xss($name) : $name; + break; + + case 'mail': + $replacements[$original] = $sanitize ? check_plain($account->mail) : $account->mail; + break; + + case 'url': + $replacements[$original] = url("user/$account->uid", $url_options); + break; + + case 'edit-url': + $replacements[$original] = url("user/$account->uid/edit", $url_options); + break; + + // These tokens are default variations on the chained tokens handled below. + case 'last-login': + $replacements[$original] = format_date($account->login, 'medium', '', NULL, $language_code); + break; + + case 'created': + $replacements[$original] = format_date($account->created, 'medium', '', NULL, $language_code); + break; + } + } + + if ($login_tokens = token_find_with_prefix($tokens, 'last-login')) { + $replacements += token_generate('date', $login_tokens, array('date' => $account->login), $options); + } + + if ($registered_tokens = token_find_with_prefix($tokens, 'created')) { + $replacements += token_generate('date', $registered_tokens, array('date' => $account->created), $options); + } + } + if ($type == 'current-user') { + global $user; + $replacements += token_generate('user', $tokens, array('user' => $user), $options); + } + + return $replacements; +} |