'; $output .= '
' . t('List of the currently available tokens on this site') . '
'; $output .= '
' . theme('token_tree', 'all', TRUE, FALSE) . '
'; $output .= ''; 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('', 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 .= ' ' . t('Warning: Token value contains raw user input.') . ''; } $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; }