Index: modules/taxonomy/taxonomy.info =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.info,v retrieving revision 1.9 diff -u -r1.9 taxonomy.info --- modules/taxonomy/taxonomy.info 8 Jun 2009 09:23:54 -0000 1.9 +++ modules/taxonomy/taxonomy.info 15 Jul 2009 22:28:00 -0000 @@ -9,3 +9,4 @@ files[] = taxonomy.pages.inc files[] = taxonomy.install files[] = taxonomy.test +files[] = taxonomy.tokens.inc Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.935 diff -u -r1.935 common.inc --- includes/common.inc 15 Jul 2009 17:40:17 -0000 1.935 +++ includes/common.inc 15 Jul 2009 22:28:00 -0000 @@ -4798,3 +4798,188 @@ } variable_set('css_js_query_string', $new_character . substr($string_history, 0, 19)); } + +/** + * Replace all tokens in a given string with appropriate values. + * + * @param $text + * A string potentially containing replacable tokens. + * @param $data + * 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 $data 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 replaements 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 generated without + * santizing unsafe data. Developers who set this option to FALSE assume + * responsibility for sanitizing all token data before it's displayed to + * end users. + * @return + * Text with tokens replaced. + */ +function token_replace($text, array $data = array(), array $options = array()) { + $token_list = token_scan($text); + $replacements = token_generate($token_list, $data, $options); + + 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 original "raw" 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 $raw_tokens + * A keyed array of tokens, and their original raw form in the source text. + * @param $data + * 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 $data 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 replaements 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. + * - unsafe: + * A boolean flag indicating that tokens should be generated without + * santizing unsafe data. Developers who set this option to TRUE assume + * responsibility for sanitizing all token data before it's displayed to + * end 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(array $raw_tokens, array $data = array(), array $options = array()) { + $results = array(); + $options += array('sanitize' => TRUE); + + foreach ($raw_tokens as $type => $tokens) { + foreach (module_invoke_all('tokens', $type, $tokens, $data, $options) 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. The following code: + * + * $data = array( + * 'author:name' => '[node:author:name]', + * 'title' => '[node:title]', + * 'created' => '[node:author:name]', + * ); + * token_find_with_prefix($data, 'author'); + * + * would result in the following return value: + * + * array('name' => '[node:author:name]'); + * + * @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: + * + * $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.'), + * 'references' => 'user', + * ); + * + * @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; +} Index: modules/statistics/statistics.info =================================================================== RCS file: /cvs/drupal/drupal/modules/statistics/statistics.info,v retrieving revision 1.9 diff -u -r1.9 statistics.info --- modules/statistics/statistics.info 8 Jun 2009 09:23:54 -0000 1.9 +++ modules/statistics/statistics.info 15 Jul 2009 22:28:00 -0000 @@ -9,3 +9,4 @@ files[] = statistics.pages.inc files[] = statistics.install files[] = statistics.test +files[] = statistics.tokens.inc Index: modules/upload/upload.info =================================================================== RCS file: /cvs/drupal/drupal/modules/upload/upload.info,v retrieving revision 1.9 diff -u -r1.9 upload.info --- modules/upload/upload.info 8 Jun 2009 09:23:54 -0000 1.9 +++ modules/upload/upload.info 15 Jul 2009 22:28:00 -0000 @@ -8,3 +8,4 @@ files[] = upload.admin.inc files[] = upload.install files[] = upload.test +files[] = upload.tokens.inc Index: modules/system/system.test =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.test,v retrieving revision 1.55 diff -u -r1.55 system.test --- modules/system/system.test 13 Jul 2009 21:51:42 -0000 1.55 +++ modules/system/system.test 15 Jul 2009 22:28:00 -0000 @@ -1062,3 +1062,45 @@ 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)); + global $user; + + $source = '[node:title]'; + $source .= '[node:author:name]'; + $source .= '[node:created:since]'; + $source .= '[user:name]'; + $source .= '[date:small]'; + $source .= '[bogus:token]'; + + $target = check_plain($node->title); + $target .= check_plain($account->name); + $target .= format_interval(REQUEST_TIME - $node->created, 2); + $target .= check_plain($user->name); + $target .= format_date(REQUEST_TIME, 'small'); + $target .= '[bogus:token]'; + + $result = token_replace($source, array('node' => $node)); + // Check that the results of token_replace() match the expected values. + $this->assertFalse(strcmp($target, $result), t('Basic placeholder tokens handled.')); + } +} Index: modules/system/system.info =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.info,v retrieving revision 1.14 diff -u -r1.14 system.info --- modules/system/system.info 17 Jun 2009 10:46:49 -0000 1.14 +++ modules/system/system.info 15 Jul 2009 22:28:00 -0000 @@ -11,4 +11,5 @@ files[] = system.install files[] = system.test files[] = system.tar.inc +files[] = system.tokens.inc required = TRUE Index: modules/poll/poll.info =================================================================== RCS file: /cvs/drupal/drupal/modules/poll/poll.info,v retrieving revision 1.9 diff -u -r1.9 poll.info --- modules/poll/poll.info 8 Jun 2009 09:23:53 -0000 1.9 +++ modules/poll/poll.info 15 Jul 2009 22:28:00 -0000 @@ -8,3 +8,4 @@ files[] = poll.pages.inc files[] = poll.install files[] = poll.test +files[] = poll.tokens.inc Index: modules/user/user.info =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.info,v retrieving revision 1.11 diff -u -r1.11 user.info --- modules/user/user.info 8 Jun 2009 09:23:55 -0000 1.11 +++ modules/user/user.info 15 Jul 2009 22:28:00 -0000 @@ -9,4 +9,5 @@ files[] = user.pages.inc files[] = user.install files[] = user.test +files[] = user.tokens.inc required = TRUE Index: modules/node/node.info =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.info,v retrieving revision 1.11 diff -u -r1.11 node.info --- modules/node/node.info 8 Jun 2009 09:23:53 -0000 1.11 +++ modules/node/node.info 15 Jul 2009 22:28:00 -0000 @@ -10,4 +10,5 @@ files[] = node.pages.inc files[] = node.install files[] = node.test +files[] = node.tokens.inc required = TRUE Index: modules/comment/comment.info =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.info,v retrieving revision 1.10 diff -u -r1.10 comment.info --- modules/comment/comment.info 8 Jun 2009 09:23:51 -0000 1.10 +++ modules/comment/comment.info 15 Jul 2009 22:28:00 -0000 @@ -10,3 +10,4 @@ files[] = comment.pages.inc files[] = comment.install files[] = comment.test +files[] = comment.tokens.inc Index: modules/upload/upload.tokens.inc =================================================================== RCS file: modules/upload/upload.tokens.inc diff -N modules/upload/upload.tokens.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/upload/upload.tokens.inc 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,45 @@ + $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 += module_invoke_all('tokens', 'file', $upload_tokens, array('file' => $upload), $options); + } + } + + return $replacements; +} + +/** + * 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.'), + 'references' => 'file', + ) + ); + return $results; +} Index: modules/user/user.tokens.inc =================================================================== RCS file: modules/user/user.tokens.inc diff -N modules/user/user.tokens.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/user/user.tokens.inc 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,144 @@ + 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') { + if (!empty($data['user'])) { + $account = $data['user']; + } + else { + $account = $user; + } + + foreach ($tokens as $name => $original) { + switch ($name) { + // Basic user account information. + case 'uid': + $replacements[$original] = $account->uid; + break; + + case 'name': + $replacements[$original] = $sanitize ? check_plain($account->name) : $account->name; + break; + + case 'mail': + $replacements[$original] = $sanitize ? check_plain($account->mail) : $account->mail; + break; + + case 'new-password': + $replacements[$original] = $account->password; + 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 += module_invoke_all('tokens', 'date', $login_tokens, array('date' => $account->login), $options); + } + + if ($registered_tokens = token_find_with_prefix($tokens, 'created')) { + $replacements += module_invoke_all('tokens', 'date', $registered_tokens, array('date' => $account->created), $options); + } + } + + return $replacements; +} + +/** + * Implement hook_token_info(). + */ +function user_token_info() { + $results = array(); + + $type = array( + 'name' => t('Users'), + 'description' => t('Tokens related to individual user accounts.'), + ); + + + $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['new-password'] = array( + 'name' => t("New password"), + 'description' => t("A newly-assigned password given to the user account on creation."), + ); + + $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."), + ); + + + // Chained tokens for users. + $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', + ); + + + $results['types']['user'] = $type; + $results['tokens']['user'] = $user; + return $results; +} Index: modules/taxonomy/taxonomy.tokens.inc =================================================================== RCS file: modules/taxonomy/taxonomy.tokens.inc diff -N modules/taxonomy/taxonomy.tokens.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/taxonomy/taxonomy.tokens.inc 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,202 @@ + $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/' . $term->tid, 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 += module_invoke_all('tokens', '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 += module_invoke_all('tokens', '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; +} + +/** + * Implement hook_token_info(). + */ +function taxonomy_token_info() { + $results = array(); + + // Metadata for token types. + $types['term'] = array( + 'name' => t("Taxonomy terms"), + 'description' => t("Tokens related to taxonomy terms."), + ); + + $types['vocabulary'] = array( + 'name' => t("Vocabularies"), + 'description' => t("Tokens related to taxonomy vocabularies."), + ); + + + // 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."), + 'references' => 'vocabulary', + ); + + $term['parent'] = array( + 'name' => t("Parent term"), + 'description' => t("The parent term of the taxonomy term, if one exists."), + 'references' => 'term', + ); + + $results['tokens']['term'] = $term; + $results['tokens']['vocabulary'] = $vocabulary; + $results['types'] = $types; + + return $results; +} Index: modules/poll/poll.tokens.inc =================================================================== RCS file: modules/poll/poll.tokens.inc diff -N modules/poll/poll.tokens.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/poll/poll.tokens.inc 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,96 @@ + 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; +} + +/** + * Implement hook_token_info(). + */ +function poll_token_info() { + $results = array(); + + $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."), + ); + + $results['tokens']['node'] = $node; + return $results; +} Index: modules/node/node.tokens.inc =================================================================== RCS file: modules/node/node.tokens.inc diff -N modules/node/node.tokens.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/node/node.tokens.inc 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,214 @@ + 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': + $replacements[$original] = $sanitize ? filter_xss($node->name) : $node->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; + } + } + + if ($author_tokens = token_find_with_prefix($tokens, 'author')) { + $author = user_load($node->uid); + $replacements += module_invoke_all('tokens', 'user', $author_tokens, array('user' => $author), $options); + } + + if ($created_tokens = token_find_with_prefix($tokens, 'created')) { + $replacements += module_invoke_all('tokens', 'date', $created_tokens, array('date' => $node->created), $options); + } + + if ($changed_tokens = token_find_with_prefix($tokens, 'changed')) { + $replacements += module_invoke_all('tokens', 'date', $changed_tokens, array('date' => $node->changed), $options); + } + } + + return $replacements; +} + +/** + * Implement hook_token_info(). + */ +function node_token_info() { + $results = array(); + + $type['name'] = t('Nodes'); + $type['description'] = t('Tokens related to individual nodes.'); + + + // 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."), + 'references' => 'user', + ); + + $results['types']['node'] = $type; + $results['tokens']['node'] = $node; + return $results; +} Index: modules/comment/comment.tokens.inc =================================================================== RCS file: modules/comment/comment.tokens.inc diff -N modules/comment/comment.tokens.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/comment/comment.tokens.inc 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,244 @@ + 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': + $replacements[$original] = $sanitize ? filter_xss($comment->name) : $comment->name; + break; + + case 'mail': + $replacements[$original] = $sanitize ? check_plain($comment->email) : $comment->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 += module_invoke_all('tokens', 'node', $node_tokens, array('node' => $node), $options); + } + + if ($date_tokens = token_find_with_prefix($tokens, 'created')) { + $replacements += module_invoke_all('tokens', 'date', $date_tokens, array('date' => $comment->timestamp), $options); + } + + if (($parent_tokens = token_find_with_prefix($tokens, 'parent')) && $parent = comment_load($comment->pid)) { + $replacements += module_invoke_all('tokens', 'comment', $parent_tokens, array('comment' => $parent), $options); + } + + if (($author_tokens = token_find_with_prefix($tokens, 'author')) && $account = user_load($comment->uid)) { + $replacements += module_invoke_all('tokens', 'user', $author_tokens, array('user' => $account), $options); + } + } + elseif ($type == 'node' & !empty($data['node'])) { + $node = $data['node']; + + foreach ($tokens as $name => $original) { + if ($name == 'comment-count') { + $replacements[$original] = $node->comment_count; + } + } + } + + return $replacements; +} + +/** + * Implement hook_token_info(). + */ +function comment_token_info() { + $results = array(); + + // Metadata for token types. + $type['name'] = t('Comments'); + $type['description'] = t('Tokens for comments posted on the site.'); + + + // Comment-related tokens for nodes + $node['comment-count'] = array( + 'name' => t("Comment count"), + 'description' => t("The number of comments posted on a node."), + ); + + + // 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."), + 'references' => 'comment', + ); + + $comment['node'] = array( + 'name' => t("Node"), + 'description' => t("The node the comment was posted to."), + 'references' => 'node', + ); + + $comment['author'] = array( + 'name' => t("Author"), + 'description' => t("The author of the comment, if they were logged in."), + 'references' => 'user', + ); + + $results['types']['comment'] = $type; + $results['tokens']['node'] = $node; + $results['tokens']['comment'] = $comment; + return $results; +} Index: modules/statistics/statistics.tokens.inc =================================================================== RCS file: modules/statistics/statistics.tokens.inc diff -N modules/statistics/statistics.tokens.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/statistics/statistics.tokens.inc 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,66 @@ + 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 += module_invoke_all('tokens', 'date', $created_tokens, array('date' => $statistics['timestamp']), $options); + } + } + + return $replacements; +} + +/** + * Implement hook_token_info(). + */ +function statistics_token_info() { + $results = array(); + + $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', + ); + + $results['tokens']['node'] = $node; + return $results; +} Index: modules/system/system.tokens.inc =================================================================== RCS file: modules/system/system.tokens.inc diff -N modules/system/system.tokens.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/system/system.tokens.inc 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,332 @@ + 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('', $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 += module_invoke_all('tokens', 'node', $node_tokens, array('node' => $node), $language, $sanitize); + } + + if ($date_tokens = token_find_with_prefix($tokens, 'timestamp')) { + $replacements += module_invoke_all('tokens', 'date', $date_tokens, array('date' => $file->timestamp), $language, $sanitize); + } + + if (($owner_tokens = token_find_with_prefix($tokens, 'owner')) && $account = user_load($file->uid)) { + $replacements += module_invoke_all('tokens', 'user', $owner_tokens, array('user' => $account), $language, $sanitize); + } + } + + return $replacements; +} + +/** + * Implement hook_token_info(). + */ +function system_token_info() { + $results = array(); + + $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."), + ); + + + // 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"), + ); + $date['large']['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."), + 'references' => 'user', + ); + + + $results['types'] = $types; + $results['tokens']['site'] = $site; + $results['tokens']['date'] = $date; + $results['tokens']['file'] = $file; + return $results; +}