3 namespace Drupal\token;
5 use Drupal\Core\Cache\Cache;
6 use Drupal\Core\Cache\CacheBackendInterface;
7 use Drupal\Core\Language\LanguageManagerInterface;
8 use Drupal\Core\Render\BubbleableMetadata;
10 class TreeBuilder implements TreeBuilderInterface {
13 * @var \Drupal\token\Token
15 protected $tokenService;
18 * @var \Drupal\token\TokenEntityMapperInterface
20 protected $entityMapper;
23 * @var \Drupal\Core\Language\LanguageManagerInterface
25 protected $languageManager;
28 * @var \Drupal\Core\Cache\CacheBackendInterface
30 protected $cacheBackend;
33 * Cache already built trees.
37 protected $builtTrees;
39 public function __construct(TokenInterface $token_service, TokenEntityMapperInterface $entity_mapper, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager) {
40 $this->tokenService = $token_service;
41 $this->entityMapper = $entity_mapper;
42 $this->cacheBackend = $cache_backend;
43 $this->languageManager = $language_manager;
49 public function buildRenderable(array $token_types, array $options = []) {
50 // Set default options.
52 'global_types' => TRUE,
53 'click_insert' => TRUE,
54 'show_restricted' => FALSE,
55 'show_nested' => FALSE,
56 'recursion_limit' => 3,
59 $info = $this->tokenService->getInfo();
60 if ($options['global_types']) {
61 $token_types = array_merge($token_types, $this->tokenService->getGlobalTokenTypes());
66 'cid' => 'tree-rendered:' . hash('sha256', serialize(['token_types' => $token_types, 'global_types' => NULL] + $variables)),
67 'tags' => [Token::TOKEN_INFO_CACHE_TAG],
71 // @todo Find a way to use the render cache for this.
72 /*if ($cached_output = token_render_cache_get($element)) {
73 return $cached_output;
78 'restricted' => $options['show_restricted'],
79 'nested' => $options['show_nested'],
80 'depth' => $options['recursion_limit'],
84 foreach ($info['types'] as $type => $type_info) {
85 if (!in_array($type, $token_types)) {
89 $token_tree[$type] = $type_info;
90 $token_tree[$type]['tokens'] = $this->buildTree($type, $tree_options);
94 '#type' => 'token_tree_table',
95 '#token_tree' => $token_tree,
96 '#show_restricted' => $options['show_restricted'],
97 '#show_nested' => $options['show_nested'],
98 '#click_insert' => $options['click_insert'],
99 '#columns' => ['name', 'token', 'description'],
100 '#empty' => t('No tokens available'),
109 public function buildAllRenderable(array $options = []) {
110 $info = $this->tokenService->getInfo();
111 $token_types = array_keys($info['types']);
113 // Disable merging in global types as we will be adding in all token types
114 // explicitly. There is no difference in leaving this set to TRUE except for
115 // an additional method call which is unnecessary.
116 $options['global_types'] = FALSE;
117 return $this->buildRenderable($token_types, $options);
123 public function buildTree($token_type, array $options = []) {
125 'restricted' => FALSE,
132 // Do not allow past the maximum token information depth.
133 $options['depth'] = min($options['depth'], static::MAX_DEPTH);
135 // If $token_type is an entity, make sure we are using the actual token type.
136 if ($entity_token_type = $this->entityMapper->getTokenTypeForEntityType($token_type)) {
137 $token_type = $entity_token_type;
140 $langcode = $this->languageManager->getCurrentLanguage()->getId();
141 $tree_cid = "token_tree:{$token_type}:{$langcode}:{$options['depth']}";
143 // If we do not have this base tree in the static cache, check the cache
144 // otherwise generate and store it in the cache.
145 if (!isset($this->builtTrees[$tree_cid])) {
146 if ($cache = $this->cacheBackend->get($tree_cid)) {
147 $this->builtTrees[$tree_cid] = $cache->data;
150 $options['parents'] = [];
151 $this->builtTrees[$tree_cid] = $this->getTokenData($token_type, $options);
152 $this->cacheBackend->set($tree_cid, $this->builtTrees[$tree_cid], Cache::PERMANENT, [Token::TOKEN_INFO_CACHE_TAG]);
156 $tree = $this->builtTrees[$tree_cid];
158 // If the user has requested a flat tree, convert it.
159 if (!empty($options['flat'])) {
160 $tree = $this->flattenTree($tree);
163 // Fill in token values.
164 if (!empty($options['values'])) {
166 foreach ($tree as $token => $token_info) {
167 if (!empty($token_info['dynamic']) || !empty($token_info['restricted'])) {
170 elseif (!isset($token_info['value'])) {
171 $token_values[$token_info['token']] = $token;
174 if (!empty($token_values)) {
175 $token_values = $this->tokenService->generate($token_type, $token_values, $options['data'], [], new BubbleableMetadata());
176 foreach ($token_values as $token => $replacement) {
177 $tree[$token]['value'] = $replacement;
188 public function flattenTree(array $tree) {
190 foreach ($tree as $token => $token_info) {
191 $result[$token] = $token_info;
192 if (isset($token_info['children']) && is_array($token_info['children'])) {
193 $result += $this->flattenTree($token_info['children']);
200 * Generate a token tree.
202 * @param string $token_type
204 * @param array $options
205 * An associative array of additional options. See documentation for
206 * TreeBuilderInterface::buildTree() for more information.
209 * The token data for the specified $token_type.
213 protected function getTokenData($token_type, array $options) {
218 $info = $this->tokenService->getInfo();
219 if ($options['depth'] <= 0 || !isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
224 foreach ($info['tokens'][$token_type] as $token => $token_info) {
225 // Build the raw token string.
226 $token_parents = $options['parents'];
227 if (empty($token_parents)) {
228 // If the parents array is currently empty, assume the token type is its
230 $token_parents[] = $token_type;
232 elseif (in_array($token, array_slice($token_parents, 1), TRUE)) {
233 // Prevent duplicate recursive tokens. For example, this will prevent
234 // the tree from generating the following tokens or deeper:
235 // [comment:parent:parent]
236 // [comment:parent:root:parent]
240 $token_parents[] = $token;
241 if (!empty($token_info['dynamic'])) {
242 $token_parents[] = '?';
244 $raw_token = '[' . implode(':', $token_parents) . ']';
245 $tree[$raw_token] = $token_info;
246 $tree[$raw_token]['raw token'] = $raw_token;
248 // Add the token's real name (leave out the base token type).
249 $tree[$raw_token]['token'] = implode(':', array_slice($token_parents, 1));
251 // Add the token's parent as its raw token value.
252 if (!empty($options['parents'])) {
253 $tree[$raw_token]['parent'] = '[' . implode(':', $options['parents']) . ']';
256 // Fetch the child tokens.
257 if (!empty($token_info['type'])) {
258 $child_options = $options;
259 $child_options['depth']--;
260 $child_options['parents'] = $token_parents;
261 $tree[$raw_token]['children'] = $this->getTokenData($token_info['type'], $child_options);