960 lines
32 KiB
Text
960 lines
32 KiB
Text
<?php
|
|
|
|
/**
|
|
* @file
|
|
* The Token API module.
|
|
*
|
|
* The Token module provides an API for providing tokens to other modules.
|
|
* Tokens are small bits of text that can be placed into larger documents
|
|
* via simple placeholders, like %site-name or [user].
|
|
*
|
|
* @ingroup token
|
|
*/
|
|
|
|
/**
|
|
* The default token prefix string.
|
|
*/
|
|
define('TOKEN_PREFIX', '[');
|
|
|
|
/**
|
|
* The default token suffix string.
|
|
*/
|
|
define('TOKEN_SUFFIX', ']');
|
|
|
|
/**
|
|
* Implements hook_help().
|
|
*/
|
|
function token_help($path, $arg) {
|
|
if ($path == 'admin/help#token') {
|
|
$output = '<dl>';
|
|
$output .= '<dt>' . t('List of the currently available tokens on this site') . '</dt>';
|
|
$output .= '<dd>' . theme('token_tree', 'all', TRUE, FALSE) . '</dd>';
|
|
$output .= '</dl>';
|
|
return $output;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return an array of the core modules supported by token.module.
|
|
*/
|
|
function _token_core_supported_modules() {
|
|
return array('node', 'user', 'taxonomy', 'comment', 'menu', 'book');
|
|
}
|
|
|
|
/**
|
|
* Implements hook_menu().
|
|
*/
|
|
function token_menu() {
|
|
$items = array();
|
|
|
|
// Devel token pages.
|
|
if (module_exists('devel')) {
|
|
$items['node/%node/devel/token'] = array(
|
|
'title' => 'Tokens',
|
|
'page callback' => 'token_devel_token_object',
|
|
'page arguments' => array('node', 1),
|
|
'access arguments' => array('access devel information'),
|
|
'type' => MENU_LOCAL_TASK,
|
|
'file' => 'token.pages.inc',
|
|
'weight' => 5,
|
|
);
|
|
$items['user/%user/devel/token'] = array(
|
|
'title' => 'Tokens',
|
|
'page callback' => 'token_devel_token_object',
|
|
'page arguments' => array('user', 1),
|
|
'access arguments' => array('access devel information'),
|
|
'type' => MENU_LOCAL_TASK,
|
|
'file' => 'token.pages.inc',
|
|
'weight' => 5,
|
|
);
|
|
}
|
|
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_theme().
|
|
*/
|
|
function token_theme() {
|
|
return array(
|
|
'token_help' => array(
|
|
'arguments' => array('type' => 'all', 'prefix' => TOKEN_PREFIX, 'suffix' => TOKEN_SUFFIX),
|
|
'file' => 'token.pages.inc',
|
|
),
|
|
'token_tree' => array(
|
|
'arguments' => array('token_types' => array(), 'global_types' => TRUE , 'click_insert' => TRUE),
|
|
'file' => 'token.pages.inc',
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_token_values().
|
|
*/
|
|
function token_token_values($type, $object = NULL) {
|
|
global $user;
|
|
$values = array();
|
|
|
|
switch ($type) {
|
|
case 'global':
|
|
// Current user tokens.
|
|
$values['user-name'] = $user->uid ? $user->name : variable_get('anonymous', t('Anonymous'));
|
|
$values['user-id'] = $user->uid ? $user->uid : 0;
|
|
$values['user-mail'] = $user->uid ? $user->mail : '';
|
|
|
|
// Site information tokens.
|
|
$values['site-url'] = url('<front>', array('absolute' => TRUE));
|
|
$values['site-name'] = check_plain(variable_get('site_name', t('Drupal')));
|
|
$values['site-slogan'] = check_plain(variable_get('site_slogan', ''));
|
|
$values['site-mission'] = filter_xss_admin(variable_get('site_mission', ''));
|
|
$values['site-mail'] = variable_get('site_mail', '');
|
|
$values += token_get_date_token_values(NULL, 'site-date-');
|
|
|
|
// Current page tokens.
|
|
$values['current-page-title'] = drupal_get_title();
|
|
$alias = drupal_get_path_alias($_GET['q']);
|
|
$values['current-page-path-raw'] = $alias;
|
|
$values['current-page-path'] = check_plain($alias);
|
|
$values['current-page-url'] = url($_GET['q'], array('absolute' => TRUE));
|
|
|
|
$page = isset($_GET['page']) ? $_GET['page'] : '';
|
|
$pager_page_array = explode(',', $page);
|
|
$page = $pager_page_array[0];
|
|
$values['current-page-number'] = (int) $page + 1;
|
|
|
|
// Backwards compatability for renamed tokens.
|
|
$values['site-date'] = $values['site-date-small'];
|
|
$values['page-number'] = $values['current-page-number'];
|
|
|
|
break;
|
|
}
|
|
return $values;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_token_list().
|
|
*/
|
|
function token_token_list($type = 'all') {
|
|
$tokens = array();
|
|
|
|
if ($type == 'global' || $type == 'all') {
|
|
// Current user tokens.
|
|
$tokens['global']['user-name'] = t('The name of the currently logged in user.');
|
|
$tokens['global']['user-id'] = t('The user ID of the currently logged in user.');
|
|
$tokens['global']['user-mail'] = t('The email address of the currently logged in user.');
|
|
|
|
// Site information tokens.
|
|
$tokens['global']['site-url'] = t("The URL of the site's front page.");
|
|
$tokens['global']['site-name'] = t('The name of the site.');
|
|
$tokens['global']['site-slogan'] = t('The slogan of the site.');
|
|
$tokens['global']['site-mission'] = t("The optional 'mission' of the site.");
|
|
$tokens['global']['site-mail'] = t('The administrative email address for the site.');
|
|
$tokens['global'] += token_get_date_token_info(t('The current'), 'site-date-');
|
|
|
|
// Current page tokens.
|
|
$tokens['global']['current-page-title'] = t('The title of the current page.');
|
|
$tokens['global']['current-page-path'] = t('The URL alias of the current page.');
|
|
$tokens['global']['current-page-path-raw'] = t('The URL alias of the current page.');
|
|
$tokens['global']['current-page-url'] = t('The URL of the current page.');
|
|
$tokens['global']['current-page-number'] = t('The page number of the current page when viewing paged lists.');
|
|
}
|
|
|
|
return $tokens;
|
|
}
|
|
|
|
/**
|
|
* General function to include the files that token relies on for the real work.
|
|
*/
|
|
function token_include() {
|
|
static $run = FALSE;
|
|
|
|
if (!$run) {
|
|
$run = TRUE;
|
|
$modules_enabled = array_keys(module_list());
|
|
$modules = array_intersect(_token_core_supported_modules(), $modules_enabled);
|
|
foreach ($modules as $module) {
|
|
module_load_include('inc', 'token', "token_$module");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replace all tokens in a given string with appropriate values.
|
|
*
|
|
* @param $text
|
|
* A string potentially containing replaceable tokens.
|
|
* @param $type
|
|
* (optional) A flag indicating the class of substitution tokens to use. If
|
|
* an object is passed in the second param, 'type' should contain the
|
|
* object's type. For example, 'node', 'comment', or 'user'. If no type is
|
|
* specified, only 'global' site-wide substitution tokens are built.
|
|
* @param $object
|
|
* (optional) An object to use for building substitution values (e.g. a node
|
|
* comment, or user object).
|
|
* @param $leading
|
|
* (optional) Character(s) to prepend to the token key before searching for
|
|
* matches. Defaults to TOKEN_PREFIX.
|
|
* @param $trailing
|
|
* (optional) Character(s) to append to the token key before searching for
|
|
* matches. Defaults to TOKEN_SUFFIX.
|
|
* @param $options
|
|
* (optional) A keyed array of settings and flags to control the token
|
|
* generation and replacement process. Supported options are:
|
|
* - clear: A boolean flag indicating that tokens should be removed from the
|
|
* final text if no replacement value can be generated.
|
|
* @param $flush
|
|
* (optional) A flag indicating whether or not to flush the token cache.
|
|
* Useful for processes that need to slog through huge numbers of tokens
|
|
* in a single execution cycle. Flushing it will keep them from burning
|
|
* through memory. Defaults to FALSE.
|
|
*
|
|
* @return
|
|
* Text with tokens replaced.
|
|
*/
|
|
function token_replace($text, $type = 'global', $object = NULL, $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX, $options = array(), $flush = FALSE) {
|
|
return token_replace_multiple($text, array($type => $object), $leading, $trailing, $options, $flush);
|
|
}
|
|
|
|
/**
|
|
* Replace all tokens in a given string with appropriate values.
|
|
*
|
|
* Contrary to token_replace() this function supports replacing multiple types.
|
|
*
|
|
* @param $text
|
|
* A string potentially containing replaceable tokens.
|
|
* @param $types
|
|
* (optional) An array of substitution classes and optional objects. The key
|
|
* is a flag indicating the class of substitution tokens to use. If an object
|
|
* is passed as value, the key should contain the object's type. For example,
|
|
* 'node', 'comment', or 'user'. The object will be used for building
|
|
* substitution values. If no type is specified, only 'global' site-wide
|
|
* substitution tokens are built.
|
|
* @param $leading
|
|
* (optional) Character(s) to prepend to the token key before searching for
|
|
* matches. Defaults to TOKEN_PREFIX.
|
|
* @param $trailing
|
|
* (optional) Character(s) to append to the token key before searching for
|
|
* matches. Defaults to TOKEN_SUFFIX.
|
|
* @param $options
|
|
* (optional) A keyed array of settings and flags to control the token
|
|
* generation and replacement process. Supported options are:
|
|
* - clear: A boolean flag indicating that tokens should be removed from the
|
|
* final text if no replacement value can be generated.
|
|
* @param $flush
|
|
* (optional) A flag indicating whether or not to flush the token cache.
|
|
* Useful for processes that need to slog through huge numbers of tokens
|
|
* in a single execution cycle. Flushing it will keep them from burning
|
|
* through memory. Defaults to FALSE.
|
|
*
|
|
* @return
|
|
* Text with tokens replaced.
|
|
*/
|
|
function token_replace_multiple($text, $types = array('global' => NULL), $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX, $options = array(), $flush = FALSE) {
|
|
// Ensure that the $text parameter is a string and not an array which is an
|
|
// invalid input.
|
|
if (is_array($text)) {
|
|
foreach ($text as $key => $value) {
|
|
$text[$key] = token_replace_multiple($value, $types, $leading, $trailing, $options, $flush);
|
|
}
|
|
return $text;
|
|
}
|
|
|
|
// If there are no tokens to replace, just return the text.
|
|
$text_tokens = token_scan($text, $leading, $trailing);
|
|
if (empty($text_tokens)) {
|
|
return $text;
|
|
}
|
|
|
|
$full = new stdClass();
|
|
$full->tokens = $full->values = array();
|
|
|
|
// Allow global token replacement by default.
|
|
if (empty($types) || !is_array($types)) {
|
|
$types = array('global' => NULL);
|
|
}
|
|
|
|
foreach ($types as $type => $object) {
|
|
$temp = token_get_values($type, $object, $flush, $options);
|
|
$full->tokens = array_merge($full->tokens, $temp->tokens);
|
|
$full->values = array_merge($full->values, $temp->values);
|
|
}
|
|
|
|
// Support clearing out tokens that would not be replaced.
|
|
if (!empty($options['clear'])) {
|
|
foreach ($text_tokens as $token) {
|
|
if (!in_array($token, $full->tokens)) {
|
|
$full->tokens[] = $token;
|
|
$full->values[] = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
$tokens = token_prepare_tokens($full->tokens, $leading, $trailing);
|
|
return str_replace($tokens, $full->values, $text);
|
|
}
|
|
|
|
/**
|
|
* Return a list of valid substitution tokens and their values for
|
|
* the specified type.
|
|
*
|
|
* @param $type
|
|
* (optional) A flag indicating the class of substitution tokens to use. If an
|
|
* object is passed in the second param, 'type' should contain the
|
|
* object's type. For example, 'node', 'comment', or 'user'. If no
|
|
* type is specified, only 'global' site-wide substitution tokens are
|
|
* built.
|
|
* @param $object
|
|
* (optional) An object to use for building substitution values (e.g. a node
|
|
* comment, or user object).
|
|
* @param $flush
|
|
* (optional) A flag indicating whether or not to flush the token cache.
|
|
* Useful for processes that need to slog through huge numbers of tokens
|
|
* in a single execution cycle. Flushing it will keep them from burning
|
|
* through memory. Defaults to FALSE.
|
|
* @param $options
|
|
* (optional) A keyed array of settings and flags to control the token
|
|
* generation process.
|
|
*
|
|
* @return
|
|
* An object with two properties:
|
|
* - tokens: All the possible tokens names generated.
|
|
* - values: The corresponding values for the tokens.
|
|
*
|
|
* Note that before performing actual token replacement that the token names
|
|
* should be run through token_prepare_tokens().
|
|
*/
|
|
function token_get_values($type = 'global', $object = NULL, $flush = FALSE, $options = array()) {
|
|
static $tokens = array();
|
|
static $running = FALSE;
|
|
|
|
// Simple recursion check. This is to avoid content_view()'s potential
|
|
// for endless looping when a filter uses tokens, which load the content
|
|
// view, which calls the filter, which uses tokens, which...
|
|
if ($running) {
|
|
// We'll allow things to get two levels deep, but bail out after that
|
|
// without performing any substitutions.
|
|
$result = new stdClass();
|
|
$result->tokens = array();
|
|
$result->values = array();
|
|
return $result;
|
|
}
|
|
else {
|
|
$running = TRUE;
|
|
}
|
|
|
|
// Flush the static token cache. Useful for processes that need to slog
|
|
// through huge numbers of tokens in a single execution cycle. Flushing it
|
|
// will keep them from burning through memory.
|
|
if ($flush || !empty($options['reset'])) {
|
|
$tokens = array();
|
|
}
|
|
|
|
// Allow simple resets of the static values.
|
|
if ($type === 'reset') {
|
|
$tokens = array();
|
|
$running = FALSE;
|
|
return;
|
|
}
|
|
|
|
// Neutralize options that do not affect token replacement.
|
|
$serialized_options = $options;
|
|
unset($serialized_options['clear']);
|
|
|
|
// Store the token cache by object ID and serialized options.
|
|
$cid = _token_get_id($type, $object) . ':' . md5(serialize($serialized_options));
|
|
if ($type != 'global' && !isset($tokens[$type][$cid])) {
|
|
token_include();
|
|
$tokens[$type][$cid] = module_invoke_all('token_values', $type, $object, $options);
|
|
}
|
|
|
|
// Special-case global tokens, as we always want to be able to process
|
|
// those substitutions.
|
|
if (!isset($tokens['global'][$cid])) {
|
|
token_include();
|
|
$tokens['global'][$cid] = module_invoke_all('token_values', 'global', NULL, $options);
|
|
}
|
|
|
|
$all = $tokens['global'][$cid];
|
|
if ($type != 'global') {
|
|
// Avoid using array_merge() if only global tokens were requested.
|
|
$all = array_merge($all, $tokens[$type][$cid]);
|
|
}
|
|
|
|
// Allow other modules to alter the replacements.
|
|
$context = array(
|
|
'type' => $type,
|
|
'object' => $object,
|
|
'options' => $options,
|
|
);
|
|
drupal_alter('token_values', $all, $context);
|
|
|
|
$result = new stdClass();
|
|
$result->tokens = array_keys($all);
|
|
$result->values = array_values($all);
|
|
|
|
$running = FALSE;
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* A helper function that retrieves all currently exposed tokens,
|
|
* and merges them recursively. This is only necessary when building
|
|
* the token listing -- during actual value replacement, only tokens
|
|
* in a particular domain are requested and a normal array_marge() is
|
|
* sufficient.
|
|
*
|
|
* @param $types
|
|
* A flag indicating the class of substitution tokens to use. If an
|
|
* object is passed in the second param, 'types' should contain the
|
|
* object's type. For example, 'node', 'comment', or 'user'. 'types'
|
|
* may also be an array of types of the form array('node','user'). If no
|
|
* type is specified, only 'global' site-wide substitution tokens are
|
|
* built.
|
|
*
|
|
* @return
|
|
* The array of usable tokens and their descriptions, organized by
|
|
* token type.
|
|
*/
|
|
function token_get_list($types = 'all') {
|
|
token_include();
|
|
$return = array();
|
|
settype($types, 'array');
|
|
foreach (module_implements('token_list') as $module) {
|
|
foreach ($types as $type) {
|
|
$module_token_list = module_invoke($module, 'token_list', $type);
|
|
if (isset($module_token_list) && is_array($module_token_list)) {
|
|
foreach ($module_token_list as $category => $tokens) {
|
|
foreach ($tokens as $token => $title) {
|
|
// Automatically append a raw token warning.
|
|
if (substr($token, -4) === '-raw' && strpos($title, t('raw user input')) === FALSE && strpos($title, t('UNIX timestamp format')) === FALSE) {
|
|
$title .= ' <em>' . t('Warning: Token value contains raw user input.') . '</em>';
|
|
}
|
|
$return[$category][$token] = $title;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Sort the tokens by name.
|
|
foreach (array_keys($return) as $category) {
|
|
ksort($return[$category]);
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* A helper function to prepare raw tokens for replacement.
|
|
*
|
|
* @param $tokens
|
|
* The array of tokens names with no delimiting characters.
|
|
* @param $leading
|
|
* String to prepend to the token. Default is TOKEN_PREFIX.
|
|
* @param $trailing
|
|
* String to append to the token. Default is TOKEN_SUFFIX.
|
|
*
|
|
* @return
|
|
* An array of the formatted tokens.
|
|
*/
|
|
function token_prepare_tokens($tokens = array(), $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX) {
|
|
foreach ($tokens as $key => $value) {
|
|
$tokens[$key] = $leading . $value . $trailing;
|
|
}
|
|
return $tokens;
|
|
}
|
|
|
|
/**
|
|
* A helper function to return an object's ID for use in static caching.
|
|
*/
|
|
function _token_get_id($type = 'global', $object = NULL) {
|
|
if (!isset($object)) {
|
|
return "default";
|
|
}
|
|
switch ($type) {
|
|
case 'node':
|
|
return isset($object->vid) ? $object->vid : (isset($object->nid) ? $object->nid : 0);
|
|
case 'comment':
|
|
return isset($object->cid) ? $object->cid : 0;
|
|
case 'user':
|
|
return isset($object->uid) ? $object->uid : 0;
|
|
case 'taxonomy':
|
|
return isset($object->tid) ? $object->tid : 0;
|
|
default:
|
|
return crc32(serialize($object));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build a list of common date tokens for use in hook_token_list().
|
|
*
|
|
* @param $description
|
|
*/
|
|
function token_get_date_token_info($description, $token_prefix = '') {
|
|
$time = time();
|
|
$tokens[$token_prefix . 'small'] = t("!description date in 'small' format. (%date)", array('!description' => $description, '%date' => format_date($time, 'small')));
|
|
$tokens[$token_prefix . 'yyyy'] = t("!description year (four digit)", array('!description' => $description));
|
|
$tokens[$token_prefix . 'yy'] = t("!description year (two digit)", array('!description' => $description));
|
|
$tokens[$token_prefix . 'month'] = t("!description month (full word)", array('!description' => $description));
|
|
$tokens[$token_prefix . 'mon'] = t("!description month (abbreviated)", array('!description' => $description));
|
|
$tokens[$token_prefix . 'mm'] = t("!description month (two digits with leading zeros)", array('!description' => $description));
|
|
$tokens[$token_prefix . 'm'] = t("!description month (one or two digits without leading zeros)", array('!description' => $description));
|
|
$tokens[$token_prefix . 'ww'] = t("!description week (two digits with leading zeros)", array('!description' => $description));
|
|
if (version_compare(PHP_VERSION, '5.1.0', '>=')) {
|
|
$tokens[$token_prefix . 'date'] = t("!description date (numeric representation of the day of the week)", array('!description' => $description));
|
|
}
|
|
$tokens[$token_prefix . 'day'] = t("!description day (full word)", array('!description' => $description));
|
|
$tokens[$token_prefix . 'ddd'] = t("!description day (abbreviation)", array('!description' => $description));
|
|
$tokens[$token_prefix . 'dd'] = t("!description day (two digits with leading zeros)", array('!description' => $description));
|
|
$tokens[$token_prefix . 'd'] = t("!description day (one or two digits without leading zeros)", array('!description' => $description));
|
|
$tokens[$token_prefix . 'raw'] = t("!description in UNIX timestamp format (%date)", array('!description' => $description, '%date' => $time));
|
|
$tokens[$token_prefix . 'since'] = t("!description in 'time-since' format. (%date)", array('!description' => $description, '%date' => format_interval($time - 360, 2)));
|
|
return $tokens;
|
|
}
|
|
|
|
/**
|
|
* Build a list of common date tokens for use in hook_token_values().
|
|
*/
|
|
function token_get_date_token_values($timestamp = NULL, $token_prefix = '', $langcode = NULL) {
|
|
static $formats;
|
|
|
|
if (!isset($formats)) {
|
|
$formats = array();
|
|
$formats['small'] = variable_get('date_format_short', 'm/d/Y - H:i');
|
|
$formats['yyyy'] = 'Y';
|
|
$formats['yy'] = 'y';
|
|
$formats['month'] = 'F';
|
|
$formats['mon'] = 'M';
|
|
$formats['mm'] = 'm';
|
|
$formats['m'] = 'n';
|
|
$formats['ww'] = 'W';
|
|
if (version_compare(PHP_VERSION, '5.1.0', '>=')) {
|
|
$formats['date'] = 'N';
|
|
}
|
|
$formats['day'] = 'l';
|
|
$formats['ddd'] = 'D';
|
|
$formats['dd'] = 'd';
|
|
$formats['d'] = 'j';
|
|
}
|
|
|
|
$time = time();
|
|
if (!isset($timestamp)) {
|
|
$timestamp = $time;
|
|
}
|
|
|
|
$tokens = array();
|
|
foreach ($formats as $token => $format) {
|
|
$tokens[$token_prefix . $token] = token_format_date($timestamp, 'custom', $format, NULL, $langcode);
|
|
}
|
|
$tokens[$token_prefix . 'raw'] = $timestamp;
|
|
$tokens[$token_prefix . 'since'] = format_interval($time - $timestamp, 2, $langcode);
|
|
|
|
return $tokens;
|
|
}
|
|
|
|
/**
|
|
* A copy of format_date() that supports the 'N' date format character.
|
|
*
|
|
* @see format_date()
|
|
*/
|
|
function token_format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
|
|
global $user;
|
|
static $timezones = array();
|
|
|
|
// Statically cache each user's timezone so it doesn't need to be re-fetched
|
|
// ever call.
|
|
if (!isset($timezones[$user->uid])) {
|
|
if (!empty($user->uid) && variable_get('configurable_timezones', 1) && strlen($user->timezone)) {
|
|
$timezones[$user->uid] = $user->timezone;
|
|
}
|
|
else {
|
|
$timezones[$user->uid] = variable_get('date_default_timezone', 0);
|
|
}
|
|
}
|
|
|
|
$timestamp += $timezones[$user->uid];
|
|
|
|
switch ($type) {
|
|
case 'custom':
|
|
// No change to format.
|
|
break;
|
|
case 'small':
|
|
$format = variable_get('date_format_short', 'm/d/Y - H:i');
|
|
break;
|
|
case 'large':
|
|
$format = variable_get('date_format_long', 'l, F j, Y - H:i');
|
|
break;
|
|
case 'medium':
|
|
default:
|
|
$format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
|
|
}
|
|
|
|
$max = strlen($format);
|
|
$date = '';
|
|
for ($i = 0; $i < $max; $i++) {
|
|
$c = $format[$i];
|
|
if (strpos('AaDlM', $c) !== FALSE) {
|
|
$date .= t(gmdate($c, $timestamp), array(), $langcode);
|
|
}
|
|
elseif ($c == 'F') {
|
|
// Special treatment for long month names: May is both an abbreviation
|
|
// and a full month name in English, but other languages have
|
|
// different abbreviations.
|
|
$date .= trim(t('!long-month-name ' . gmdate($c, $timestamp), array('!long-month-name' => ''), $langcode));
|
|
}
|
|
elseif (strpos('BdgGhHiIjLmnNsStTUwWYyz', $c) !== FALSE) {
|
|
// This condition was modified to allow the 'N' date format character.
|
|
$date .= gmdate($c, $timestamp);
|
|
}
|
|
elseif ($c == 'r') {
|
|
$date .= token_format_date($timestamp - $timezone, 'custom', 'D, d M Y H:i:s O', $timezone, $langcode);
|
|
}
|
|
elseif ($c == 'O') {
|
|
$date .= sprintf('%s%02d%02d', ($timezone < 0 ? '-' : '+'), abs($timezone / 3600), abs($timezone % 3600) / 60);
|
|
}
|
|
elseif ($c == 'Z') {
|
|
$date .= $timezone;
|
|
}
|
|
elseif ($c == '\\') {
|
|
$date .= $format[++$i];
|
|
}
|
|
else {
|
|
$date .= $c;
|
|
}
|
|
}
|
|
|
|
return $date;
|
|
}
|
|
|
|
/**
|
|
* Validate an tokens in raw text based on possible contexts.
|
|
*
|
|
* @param $value
|
|
* A string with the raw text containing the raw tokens, or an array of
|
|
* tokens from token_scan().
|
|
* @param $valid_types
|
|
* An array of token types to validage against.
|
|
* @param $leading
|
|
* Character(s) to prepend to the token key before searching for
|
|
* matches. Defaults to TOKEN_PREFIX.
|
|
* @param $trailing
|
|
* Character(s) to append to the token key before searching for
|
|
* matches. Defaults to TOKEN_SUFFIX.
|
|
*
|
|
* @return
|
|
* An array with the invalid tokens in their original raw forms.
|
|
*/
|
|
function token_get_invalid_tokens_by_context($value, $valid_types = array(), $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX) {
|
|
if (in_array('all', $valid_types)) {
|
|
$valid_types = array('all');
|
|
}
|
|
else {
|
|
// Add the token types that are always valid in global context.
|
|
$valid_types[] = 'global';
|
|
}
|
|
|
|
$invalid_tokens = array();
|
|
$valid_tokens = array();
|
|
$value_tokens = is_string($value) ? token_scan($value, $leading, $trailing) : $value;
|
|
|
|
foreach (token_get_list($valid_types) as $category => $tokens) {
|
|
$valid_tokens += $tokens;
|
|
}
|
|
|
|
foreach ($value_tokens as $token) {
|
|
if (isset($valid_tokens[$token])) {
|
|
continue;
|
|
}
|
|
elseif (preg_match('/^(.*[_-])([^-_])+$/', $token, $matches)) {
|
|
// Allow tokens that do not have a direct match to tokens listed in
|
|
// hook_token_info() to be matched against a 'wildcard' token name.
|
|
if (isset($valid_tokens[$matches[1] . '?'])) {
|
|
// [token-name-?] wildcards.
|
|
continue;
|
|
}
|
|
elseif (isset($valid_tokens[$matches[1] . '????'])) {
|
|
// [token-name-????] wildcards.
|
|
continue;
|
|
}
|
|
elseif (is_numeric($matches[2]) && isset($valid_tokens[$matches[1] . 'N'])) {
|
|
// [token-name-N] wildcards if N is a numeric value.
|
|
continue;
|
|
}
|
|
}
|
|
$invalid_tokens[] = $token;
|
|
}
|
|
|
|
array_unique($invalid_tokens);
|
|
$invalid_tokens = token_prepare_tokens($invalid_tokens, $leading, $trailing);
|
|
return $invalid_tokens;
|
|
}
|
|
|
|
/**
|
|
* Build a list of all token-like patterns that appear in the text.
|
|
*
|
|
* @param $text
|
|
* The text to be scanned for possible tokens.
|
|
* @param $leading
|
|
* Character(s) to prepend to the token key before searching for
|
|
* matches. Defaults to TOKEN_PREFIX.
|
|
* @param $trailing
|
|
* Character(s) to append to the token key before searching for
|
|
* matches. Defaults to TOKEN_SUFFIX.
|
|
*
|
|
* @return
|
|
* An array of discovered tokens.
|
|
*/
|
|
function token_scan($text, $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX) {
|
|
$leadingregex = preg_quote($leading, '/');
|
|
$trailingregex = preg_quote($trailing, '/');
|
|
|
|
$regex = '/' . $leadingregex;
|
|
$regex .= '([^\s';
|
|
if (drupal_strlen($leading) == 1) {
|
|
// Only add the leading string as a non-match if it is a single character.
|
|
$regex .= $leadingregex;
|
|
}
|
|
if (drupal_strlen($trailing) == 1) {
|
|
// Only add the trailing string as a non-match if it is a single character.
|
|
$regex .= $trailingregex;
|
|
}
|
|
$regex .= ']+)' . $trailingregex . '/x';
|
|
|
|
preg_match_all($regex, $text, $matches);
|
|
return $matches[1];
|
|
}
|
|
|
|
/**
|
|
* Validate a form element that should have tokens in it.
|
|
*
|
|
* Form elements that want to add this validation should have the #token_types
|
|
* parameter defined.
|
|
*
|
|
* For example:
|
|
* @code
|
|
* $form['my_node_text_element'] = array(
|
|
* '#type' => 'textfield',
|
|
* '#title' => t('Some text to token-ize that has a node context.'),
|
|
* '#default_value' => 'The title of this node is [title].',
|
|
* '#element_validate' => array('token_element_validate'),
|
|
* '#token_types' => array('node'),
|
|
* '#min_tokens' => 1,
|
|
* '#max_tokens' => 10,
|
|
* );
|
|
* @endcode
|
|
*/
|
|
function token_element_validate(&$element, &$form_state) {
|
|
$value = isset($element['#value']) ? $element['#value'] : $element['#default_value'];
|
|
|
|
if (!drupal_strlen($value)) {
|
|
// Empty value needs no further validation since the element should depend
|
|
// on using the '#required' FAPI property.
|
|
return $element;
|
|
}
|
|
|
|
$tokens = token_scan($value);
|
|
$title = empty($element['#title']) ? $element['#parents'][0] : $element['#title'];
|
|
|
|
// Validate if an element must have a minimum number of tokens.
|
|
if (isset($element['#min_tokens']) && count($tokens) < $element['#min_tokens']) {
|
|
// @todo Change this error message to include the minimum number.
|
|
$error = format_plural($element['#min_tokens'], 'The %element-title cannot contain fewer than one token.', 'The %element-title must contain at least @count tokens.', array('%element-title' => $title));
|
|
form_error($element, $error);
|
|
}
|
|
|
|
// Validate if an element must have a maximum number of tokens.
|
|
if (isset($element['#max_tokens']) && count($tokens) > $element['#max_tokens']) {
|
|
// @todo Change this error message to include the maximum number.
|
|
$error = format_plural($element['#max_tokens'], 'The %element-title must contain as most one token.', 'The %element-title must contain at most @count tokens.', array('%element-title' => $title));
|
|
form_error($element, $error);
|
|
}
|
|
|
|
// Check if the field defines specific token types.
|
|
if (!empty($element['#token_types'])) {
|
|
$invalid_tokens = token_get_invalid_tokens_by_context($tokens, $element['#token_types']);
|
|
if ($invalid_tokens) {
|
|
form_error($element, t('The %element-title is using the following invalid tokens: @invalid-tokens.', array('%element-title' => $title, '@invalid-tokens' => implode(', ', $invalid_tokens))));
|
|
}
|
|
}
|
|
|
|
return $element;
|
|
}
|
|
|
|
/**
|
|
* Deprecated. Use token_element_validate() instead.
|
|
*/
|
|
function token_element_validate_token_context(&$element, &$form_state) {
|
|
return token_element_validate($element, $form_state);
|
|
}
|
|
|
|
/**
|
|
* Find tokens that have been declared twice by different modules.
|
|
*/
|
|
function token_find_duplicate_tokens() {
|
|
token_include();
|
|
$all_tokens = array();
|
|
|
|
foreach (module_implements('token_list') as $module) {
|
|
$module_token_list = module_invoke($module, 'token_list', 'all');
|
|
if (!isset($module_token_list) || !is_array($module_token_list)) {
|
|
// Skip modules that do not return an array as that is a valid return
|
|
// value.
|
|
continue;
|
|
}
|
|
if (in_array($module, _token_core_supported_modules())) {
|
|
$module = 'token';
|
|
}
|
|
foreach ($module_token_list as $type => $tokens) {
|
|
foreach (array_keys($tokens) as $token) {
|
|
$all_tokens[$type . ':' . $token][] = $module;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($all_tokens as $token => $modules) {
|
|
if (count($modules) < 2) {
|
|
unset($all_tokens[$token]);
|
|
}
|
|
}
|
|
|
|
return $all_tokens;
|
|
}
|
|
|
|
/**
|
|
* Get a translated menu link by its mlid, without access checking.
|
|
*
|
|
* This function is a copy of menu_link_load() but with its own cache and a
|
|
* simpler query to load the link. This also skips normal menu link access
|
|
* checking by using _token_menu_link_translate().
|
|
*
|
|
* @param $mlid
|
|
* The mlid of the menu item.
|
|
*
|
|
* @return
|
|
* A menu link translated for rendering.
|
|
*
|
|
* @see menu_link_load()
|
|
* @see _token_menu_link_translate()
|
|
*/
|
|
function token_menu_link_load($mlid) {
|
|
static $cache = array();
|
|
|
|
if (!is_numeric($mlid)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (!isset($cache[$mlid])) {
|
|
$item = db_fetch_array(db_query("SELECT * FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid));
|
|
if (!empty($item)) {
|
|
_token_menu_link_translate($item);
|
|
}
|
|
$cache[$mlid] = $item;
|
|
}
|
|
|
|
return $cache[$mlid];
|
|
}
|
|
|
|
/**
|
|
* Get a translated book menu link by its mlid, without access checking.
|
|
*
|
|
* This function is a copy of book_link_load() but with its own cache and a
|
|
* simpler query to load the link. This also skips normal menu link access
|
|
* checking by using _token_menu_link_translate().
|
|
*
|
|
* @param $mlid
|
|
* The mlid of the book menu item.
|
|
*
|
|
* @return
|
|
* A book menu link translated for rendering.
|
|
*
|
|
* @see book_link_load()
|
|
* @see _token_menu_link_translate()
|
|
*/
|
|
function token_book_link_load($mlid) {
|
|
static $cache = array();
|
|
|
|
if (!is_numeric($mlid)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (!isset($cache[$mlid])) {
|
|
$item = db_fetch_array(db_query("SELECT * FROM {menu_links} ml INNER JOIN {book} b ON b.mlid = ml.mlid LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = %d", $mlid));
|
|
if (!empty($item)) {
|
|
_token_menu_link_translate($item);
|
|
}
|
|
$cache[$mlid] = $item;
|
|
}
|
|
|
|
return $cache[$mlid];
|
|
}
|
|
|
|
function _token_menu_link_translate(&$item) {
|
|
$map = array();
|
|
|
|
if (!is_array($item['options'])) {
|
|
$item['options'] = unserialize($item['options']);
|
|
}
|
|
|
|
if ($item['external']) {
|
|
$item['access'] = 1;
|
|
$item['href'] = $item['link_path'];
|
|
$item['title'] = $item['link_title'];
|
|
$item['localized_options'] = $item['options'];
|
|
}
|
|
else {
|
|
$map = explode('/', $item['link_path']);
|
|
_menu_link_map_translate($map, $item['to_arg_functions']);
|
|
$item['href'] = implode('/', $map);
|
|
|
|
// Note - skip callbacks without real values for their arguments.
|
|
if (strpos($item['href'], '%') !== FALSE) {
|
|
$item['access'] = FALSE;
|
|
return FALSE;
|
|
}
|
|
|
|
$item['access'] = TRUE;
|
|
_menu_item_localize($item, $map, TRUE);
|
|
}
|
|
|
|
// Allow other customizations - e.g. adding a page-specific query string to the
|
|
// options array. For performance reasons we only invoke this hook if the link
|
|
// has the 'alter' flag set in the options array.
|
|
if (!empty($item['options']['alter'])) {
|
|
drupal_alter('translated_menu_link', $item, $map);
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
/**
|
|
* Find all ancestors of a given menu link ID.
|
|
*
|
|
* @param $mlid
|
|
* A menu link ID.
|
|
*
|
|
* @return
|
|
* An array of menu links from token_menu_link_load() with the root link
|
|
* first, and the menu link with ID $mlid last.
|
|
*/
|
|
function token_menu_link_get_parents_all($mlid) {
|
|
$parents = array();
|
|
|
|
while (!empty($mlid)) {
|
|
$link = token_menu_link_load($mlid);
|
|
array_unshift($parents, $link);
|
|
$mlid = $link['plid'];
|
|
}
|
|
|
|
return $parents;
|
|
}
|
|
|
|
/**
|
|
* Deprecated. Use the raw return value of token_menu_link_get_parents_all() instead.
|
|
*/
|
|
function _menu_titles($menu_link, $nid) {
|
|
$titles = array();
|
|
$parents = token_menu_link_get_parents_all($menu_link['mlid']);
|
|
foreach ($parents as $mlid => $parent) {
|
|
$titles[] = $parent['title'];
|
|
}
|
|
return $titles;
|
|
}
|