3 namespace Drupal\token;
5 use Drupal\Core\Cache\CacheBackendInterface;
6 use Drupal\Core\Language\LanguageInterface;
7 use Drupal\Core\Utility\Token as TokenBase;
10 * Service to retrieve token information.
12 * This service replaces the core's token service and provides the same
13 * functionality by extending it. It also provides additional functionality
14 * commonly required by the additional support provided by token module and
17 class Token extends TokenBase implements TokenInterface {
23 * An array of token definitions, or NULL when the definitions are not set.
25 * @see self::resetInfo()
27 protected $globalTokenTypes;
32 public function getInfo() {
33 if (empty($this->tokenInfo)) {
34 $cache_id = 'token_info_sorted:' . $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
35 $cache = $this->cache->get($cache_id);
37 $this->tokenInfo = $cache->data;
40 $token_info = $this->moduleHandler->invokeAll('token_info');
41 $this->moduleHandler->alter('token_info', $token_info);
43 foreach (array_keys($token_info['types']) as $type_key) {
44 if (isset($token_info['types'][$type_key]['type'])) {
45 $base_type = $token_info['types'][$type_key]['type'];
46 // If this token type extends another token type, then merge in
47 // the base token type's tokens.
48 if (isset($token_info['tokens'][$base_type])) {
49 $token_info['tokens'] += [$type_key => []];
50 $token_info['tokens'][$type_key] += $token_info['tokens'][$base_type];
54 // Add a 'type' value to each token type information.
55 $token_info['types'][$type_key]['type'] = $type_key;
60 $by_name = $this->prepareMultisort($token_info['types']);
61 array_multisort($by_name, SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $token_info['types']);
62 foreach (array_keys($token_info['tokens']) as $type) {
63 $by_name = $this->prepareMultisort($token_info['tokens'][$type]);
64 array_multisort($by_name, SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $token_info['tokens'][$type]);
67 $this->tokenInfo = $token_info;
68 $this->cache->set($cache_id, $this->tokenInfo, CacheBackendInterface::CACHE_PERMANENT, array(
69 static::TOKEN_INFO_CACHE_TAG,
74 return $this->tokenInfo;
78 * Extracts data from the token data for use in array_multisort().
80 * @param array $token_info
81 * List of tokens or token types, each element must have a name key.
84 * List of the names keyed by the token key.
86 protected function prepareMultisort($token_info) {
88 foreach ($token_info as $key => $token_info_element) {
89 $by_name[$key] = $token_info_element['name'];
97 public function getTokenInfo($token_type, $token) {
98 if (empty($this->tokenInfo)) {
102 return isset($this->tokenInfo['tokens'][$token_type][$token]) ? $this->tokenInfo['tokens'][$token_type][$token] : NULL;
108 public function getTypeInfo($token_type) {
109 if (empty($this->tokenInfo)) {
113 return isset($this->tokenInfo['types'][$token_type]) ? $this->tokenInfo['types'][$token_type] : NULL;
119 public function getGlobalTokenTypes() {
120 if (empty($this->globalTokenTypes)) {
121 $token_info = $this->getInfo();
122 foreach ($token_info['types'] as $type => $type_info) {
123 // If the token types has not specified that 'needs-data' => TRUE, then
124 // it is a global token type that will always be replaced in any context.
125 if (empty($type_info['needs-data'])) {
126 $this->globalTokenTypes[] = $type;
131 return $this->globalTokenTypes;
137 function getInvalidTokens($type, $tokens) {
138 $token_info = $this->getInfo();
139 $invalid_tokens = array();
141 foreach ($tokens as $token => $full_token) {
142 if (isset($token_info['tokens'][$type][$token])) {
146 // Split token up if it has chains.
147 $parts = explode(':', $token, 2);
149 if (!isset($token_info['tokens'][$type][$parts[0]])) {
150 // This is an invalid token (not defined).
151 $invalid_tokens[] = $full_token;
153 elseif (count($parts) == 2) {
154 $sub_token_info = $token_info['tokens'][$type][$parts[0]];
155 if (!empty($sub_token_info['dynamic'])) {
156 // If this token has been flagged as a dynamic token, skip it.
159 elseif (empty($sub_token_info['type'])) {
160 // If the token has chains, but does not support it, it is invalid.
161 $invalid_tokens[] = $full_token;
164 // Recursively check the chained tokens.
165 $sub_tokens = $this->findWithPrefix(array($token => $full_token), $parts[0]);
166 $invalid_tokens = array_merge($invalid_tokens, $this->getInvalidTokens($sub_token_info['type'], $sub_tokens));
171 return $invalid_tokens;
177 public function getInvalidTokensByContext($value, array $valid_types = []) {
178 if (in_array('all', $valid_types)) {
179 $info = $this->getInfo();
180 $valid_types = array_keys($info['types']);
183 // Add the token types that are always valid in global context.
184 $valid_types = array_merge($valid_types, $this->getGlobalTokenTypes());
187 $invalid_tokens = array();
188 $value_tokens = is_string($value) ? $this->scan($value) : $value;
190 foreach ($value_tokens as $type => $tokens) {
191 if (!in_array($type, $valid_types)) {
192 // If the token type is not a valid context, its tokens are invalid.
193 $invalid_tokens = array_merge($invalid_tokens, array_values($tokens));
196 // Check each individual token for validity.
197 $invalid_tokens = array_merge($invalid_tokens, $this->getInvalidTokens($type, $tokens));
201 array_unique($invalid_tokens);
202 return $invalid_tokens;
208 public function resetInfo() {
210 $this->globalTokenTypes = NULL;