token.tokens.inc 67 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786
  1. <?php
  2. /**
  3. * @file
  4. * Token callbacks for the token module.
  5. */
  6. use Drupal\Core\Entity\ContentEntityInterface;
  7. use Drupal\Core\Entity\FieldableEntityInterface;
  8. use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
  9. use Drupal\Core\Render\BubbleableMetadata;
  10. use Drupal\Core\Render\Element;
  11. use Drupal\Component\Utility\Crypt;
  12. use Drupal\Component\Utility\Html;
  13. use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
  14. use Drupal\Core\Url;
  15. use Drupal\field\FieldStorageConfigInterface;
  16. use Drupal\menu_link_content\MenuLinkContentInterface;
  17. use Drupal\node\Entity\Node;
  18. use Drupal\node\Entity\NodeType;
  19. use Drupal\node\NodeInterface;
  20. use Drupal\system\Entity\Menu;
  21. use Drupal\user\UserInterface;
  22. use Symfony\Cmf\Component\Routing\RouteObjectInterface;
  23. use Drupal\Core\TypedData\PrimitiveInterface;
  24. use Drupal\Core\Field\FieldStorageDefinitionInterface;
  25. use Drupal\Core\Entity\ContentEntityTypeInterface;
  26. use Drupal\image\Entity\ImageStyle;
  27. /**
  28. * Implements hook_token_info_alter().
  29. */
  30. function token_token_info_alter(&$info) {
  31. // Force 'date' type tokens to require input and add a 'current-date' type.
  32. // @todo Remove when http://drupal.org/node/943028 is fixed.
  33. $info['types']['date']['needs-data'] = 'date';
  34. $info['types']['current-date'] = [
  35. 'name' => t('Current date'),
  36. 'description' => t('Tokens related to the current date and time.'),
  37. 'type' => 'date',
  38. ];
  39. // Add a 'dynamic' key to any tokens that have chained but dynamic tokens.
  40. $info['tokens']['date']['custom']['dynamic'] = TRUE;
  41. // The [file:size] may not always return in kilobytes.
  42. // @todo Remove when http://drupal.org/node/1193044 is fixed.
  43. if (!empty($info['tokens']['file']['size'])) {
  44. $info['tokens']['file']['size']['description'] = t('The size of the file.');
  45. }
  46. // Remove deprecated tokens from being listed.
  47. unset($info['tokens']['node']['tnid']);
  48. unset($info['tokens']['node']['type']);
  49. unset($info['tokens']['node']['type-name']);
  50. // Support 'url' type tokens for core tokens.
  51. if (isset($info['tokens']['comment']['url']) && \Drupal::moduleHandler()->moduleExists('comment')) {
  52. $info['tokens']['comment']['url']['type'] = 'url';
  53. }
  54. if (isset($info['tokens']['node']['url']) && \Drupal::moduleHandler()->moduleExists('node')) {
  55. $info['tokens']['node']['url']['type'] = 'url';
  56. }
  57. if (isset($info['tokens']['term']['url']) && \Drupal::moduleHandler()->moduleExists('taxonomy')) {
  58. $info['tokens']['term']['url']['type'] = 'url';
  59. }
  60. $info['tokens']['user']['url']['type'] = 'url';
  61. // Add [token:url] tokens for any URI-able entities.
  62. $entities = \Drupal::entityTypeManager()->getDefinitions();
  63. foreach ($entities as $entity => $entity_info) {
  64. // Do not generate tokens if the entity doesn't define a token type or is
  65. // not a content entity.
  66. if (!$entity_info->get('token_type') || (!$entity_info instanceof ContentEntityTypeInterface)) {
  67. continue;
  68. }
  69. $token_type = $entity_info->get('token_type');
  70. if (!isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
  71. // Define tokens for entity type's without their own integration.
  72. $info['types'][$entity_info->id()] = [
  73. 'name' => $entity_info->getLabel(),
  74. 'needs-data' => $entity_info->id(),
  75. 'module' => 'token',
  76. ];
  77. }
  78. // Add [entity:url] tokens if they do not already exist.
  79. // @todo Support entity:label
  80. if (!isset($info['tokens'][$token_type]['url'])) {
  81. $info['tokens'][$token_type]['url'] = [
  82. 'name' => t('URL'),
  83. 'description' => t('The URL of the @entity.', ['@entity' => mb_strtolower($entity_info->getLabel())]),
  84. 'module' => 'token',
  85. 'type' => 'url',
  86. ];
  87. }
  88. // Add [entity:original] tokens if they do not already exist.
  89. if (!isset($info['tokens'][$token_type]['original'])) {
  90. $info['tokens'][$token_type]['original'] = [
  91. 'name' => t('Original @entity', ['@entity' => mb_strtolower($entity_info->getLabel())]),
  92. 'description' => t('The original @entity data if the @entity is being updated or saved.', ['@entity' => mb_strtolower($entity_info->getLabel())]),
  93. 'module' => 'token',
  94. 'type' => $token_type,
  95. ];
  96. }
  97. }
  98. // Add support for custom date formats.
  99. // @todo Remove when http://drupal.org/node/1173706 is fixed.
  100. $date_format_types = \Drupal::entityTypeManager()->getStorage('date_format')->loadMultiple();
  101. foreach ($date_format_types as $date_format_type => $date_format_type_info) {
  102. /* @var \Drupal\system\Entity\DateFormat $date_format_type_info */
  103. if (!isset($info['tokens']['date'][$date_format_type])) {
  104. $info['tokens']['date'][$date_format_type] = [
  105. 'name' => Html::escape($date_format_type_info->label()),
  106. 'description' => t("A date in '@type' format. (%date)", ['@type' => $date_format_type, '%date' => \Drupal::service('date.formatter')->format(\Drupal::time()->getRequestTime(), $date_format_type)]),
  107. 'module' => 'token',
  108. ];
  109. }
  110. }
  111. }
  112. /**
  113. * Implements hook_token_info().
  114. */
  115. function token_token_info() {
  116. // Node tokens.
  117. $info['tokens']['node']['source'] = [
  118. 'name' => t('Translation source node'),
  119. 'description' => t("The source node for this current node's translation set."),
  120. 'type' => 'node',
  121. ];
  122. $info['tokens']['node']['log'] = [
  123. 'name' => t('Revision log message'),
  124. 'description' => t('The explanation of the most recent changes made to the node.'),
  125. ];
  126. $info['tokens']['node']['content-type'] = [
  127. 'name' => t('Content type'),
  128. 'description' => t('The content type of the node.'),
  129. 'type' => 'content-type',
  130. ];
  131. // Content type tokens.
  132. $info['types']['content-type'] = [
  133. 'name' => t('Content types'),
  134. 'description' => t('Tokens related to content types.'),
  135. 'needs-data' => 'node_type',
  136. ];
  137. $info['tokens']['content-type']['name'] = [
  138. 'name' => t('Name'),
  139. 'description' => t('The name of the content type.'),
  140. ];
  141. $info['tokens']['content-type']['machine-name'] = [
  142. 'name' => t('Machine-readable name'),
  143. 'description' => t('The unique machine-readable name of the content type.'),
  144. ];
  145. $info['tokens']['content-type']['description'] = [
  146. 'name' => t('Description'),
  147. 'description' => t('The optional description of the content type.'),
  148. ];
  149. $info['tokens']['content-type']['node-count'] = [
  150. 'name' => t('Node count'),
  151. 'description' => t('The number of nodes belonging to the content type.'),
  152. ];
  153. $info['tokens']['content-type']['edit-url'] = [
  154. 'name' => t('Edit URL'),
  155. 'description' => t("The URL of the content type's edit page."),
  156. // 'type' => 'url',
  157. ];
  158. // Taxonomy term and vocabulary tokens.
  159. if (\Drupal::moduleHandler()->moduleExists('taxonomy')) {
  160. $info['tokens']['term']['edit-url'] = [
  161. 'name' => t('Edit URL'),
  162. 'description' => t("The URL of the taxonomy term's edit page."),
  163. // 'type' => 'url',
  164. ];
  165. $info['tokens']['term']['parents'] = [
  166. 'name' => t('Parents'),
  167. 'description' => t("An array of all the term's parents, starting with the root."),
  168. 'type' => 'array',
  169. ];
  170. $info['tokens']['term']['root'] = [
  171. 'name' => t('Root term'),
  172. 'description' => t("The root term of the taxonomy term."),
  173. 'type' => 'term',
  174. ];
  175. $info['tokens']['vocabulary']['machine-name'] = [
  176. 'name' => t('Machine-readable name'),
  177. 'description' => t('The unique machine-readable name of the vocabulary.'),
  178. ];
  179. $info['tokens']['vocabulary']['edit-url'] = [
  180. 'name' => t('Edit URL'),
  181. 'description' => t("The URL of the vocabulary's edit page."),
  182. // 'type' => 'url',
  183. ];
  184. }
  185. // File tokens.
  186. $info['tokens']['file']['basename'] = [
  187. 'name' => t('Base name'),
  188. 'description' => t('The base name of the file.'),
  189. ];
  190. $info['tokens']['file']['extension'] = [
  191. 'name' => t('Extension'),
  192. 'description' => t('The extension of the file.'),
  193. ];
  194. $info['tokens']['file']['size-raw'] = [
  195. 'name' => t('File byte size'),
  196. 'description' => t('The size of the file, in bytes.'),
  197. ];
  198. // User tokens.
  199. // Add information on the restricted user tokens.
  200. $info['tokens']['user']['cancel-url'] = [
  201. 'name' => t('Account cancellation URL'),
  202. 'description' => t('The URL of the confirm delete page for the user account.'),
  203. 'restricted' => TRUE,
  204. // 'type' => 'url',
  205. ];
  206. $info['tokens']['user']['one-time-login-url'] = [
  207. 'name' => t('One-time login URL'),
  208. 'description' => t('The URL of the one-time login page for the user account.'),
  209. 'restricted' => TRUE,
  210. // 'type' => 'url',
  211. ];
  212. $info['tokens']['user']['roles'] = [
  213. 'name' => t('Roles'),
  214. 'description' => t('The user roles associated with the user account.'),
  215. 'type' => 'array',
  216. ];
  217. // Current user tokens.
  218. $info['tokens']['current-user']['ip-address'] = [
  219. 'name' => t('IP address'),
  220. 'description' => t('The IP address of the current user.'),
  221. ];
  222. // Menu link tokens (work regardless if menu module is enabled or not).
  223. $info['types']['menu-link'] = [
  224. 'name' => t('Menu links'),
  225. 'description' => t('Tokens related to menu links.'),
  226. 'needs-data' => 'menu-link',
  227. ];
  228. $info['tokens']['menu-link']['mlid'] = [
  229. 'name' => t('Link ID'),
  230. 'description' => t('The unique ID of the menu link.'),
  231. ];
  232. $info['tokens']['menu-link']['title'] = [
  233. 'name' => t('Title'),
  234. 'description' => t('The title of the menu link.'),
  235. ];
  236. $info['tokens']['menu-link']['url'] = [
  237. 'name' => t('URL'),
  238. 'description' => t('The URL of the menu link.'),
  239. 'type' => 'url',
  240. ];
  241. $info['tokens']['menu-link']['parent'] = [
  242. 'name' => t('Parent'),
  243. 'description' => t("The menu link's parent."),
  244. 'type' => 'menu-link',
  245. ];
  246. $info['tokens']['menu-link']['parents'] = [
  247. 'name' => t('Parents'),
  248. 'description' => t("An array of all the menu link's parents, starting with the root."),
  249. 'type' => 'array',
  250. ];
  251. $info['tokens']['menu-link']['root'] = [
  252. 'name' => t('Root'),
  253. 'description' => t("The menu link's root."),
  254. 'type' => 'menu-link',
  255. ];
  256. // Current page tokens.
  257. $info['types']['current-page'] = [
  258. 'name' => t('Current page'),
  259. 'description' => t('Tokens related to the current page request.'),
  260. ];
  261. $info['tokens']['current-page']['title'] = [
  262. 'name' => t('Title'),
  263. 'description' => t('The title of the current page.'),
  264. ];
  265. $info['tokens']['current-page']['url'] = [
  266. 'name' => t('URL'),
  267. 'description' => t('The URL of the current page.'),
  268. 'type' => 'url',
  269. ];
  270. $info['tokens']['current-page']['page-number'] = [
  271. 'name' => t('Page number'),
  272. 'description' => t('The page number of the current page when viewing paged lists.'),
  273. ];
  274. $info['tokens']['current-page']['query'] = [
  275. 'name' => t('Query string value'),
  276. 'description' => t('The value of a specific query string field of the current page.'),
  277. 'dynamic' => TRUE,
  278. ];
  279. // URL tokens.
  280. $info['types']['url'] = [
  281. 'name' => t('URL'),
  282. 'description' => t('Tokens related to URLs.'),
  283. 'needs-data' => 'path',
  284. ];
  285. $info['tokens']['url']['path'] = [
  286. 'name' => t('Path'),
  287. 'description' => t('The path component of the URL.'),
  288. ];
  289. $info['tokens']['url']['relative'] = [
  290. 'name' => t('Relative URL'),
  291. 'description' => t('The relative URL.'),
  292. ];
  293. $info['tokens']['url']['absolute'] = [
  294. 'name' => t('Absolute URL'),
  295. 'description' => t('The absolute URL.'),
  296. ];
  297. $info['tokens']['url']['brief'] = [
  298. 'name' => t('Brief URL'),
  299. 'description' => t('The URL without the protocol and trailing backslash.'),
  300. ];
  301. $info['tokens']['url']['unaliased'] = [
  302. 'name' => t('Unaliased URL'),
  303. 'description' => t('The unaliased URL.'),
  304. 'type' => 'url',
  305. ];
  306. $info['tokens']['url']['args'] = [
  307. 'name' => t('Arguments'),
  308. 'description' => t("The specific argument of the current page (e.g. 'arg:1' on the page 'node/1' returns '1')."),
  309. 'type' => 'array',
  310. ];
  311. // Array tokens.
  312. $info['types']['array'] = [
  313. 'name' => t('Array'),
  314. 'description' => t('Tokens related to arrays of strings.'),
  315. 'needs-data' => 'array',
  316. 'nested' => TRUE,
  317. ];
  318. $info['tokens']['array']['first'] = [
  319. 'name' => t('First'),
  320. 'description' => t('The first element of the array.'),
  321. ];
  322. $info['tokens']['array']['last'] = [
  323. 'name' => t('Last'),
  324. 'description' => t('The last element of the array.'),
  325. ];
  326. $info['tokens']['array']['count'] = [
  327. 'name' => t('Count'),
  328. 'description' => t('The number of elements in the array.'),
  329. ];
  330. $info['tokens']['array']['reversed'] = [
  331. 'name' => t('Reversed'),
  332. 'description' => t('The array reversed.'),
  333. 'type' => 'array',
  334. ];
  335. $info['tokens']['array']['keys'] = [
  336. 'name' => t('Keys'),
  337. 'description' => t('The array of keys of the array.'),
  338. 'type' => 'array',
  339. ];
  340. $info['tokens']['array']['join'] = [
  341. 'name' => t('Imploded'),
  342. 'description' => t('The values of the array joined together with a custom string in-between each value.'),
  343. 'dynamic' => TRUE,
  344. ];
  345. $info['tokens']['array']['value'] = [
  346. 'name' => t('Value'),
  347. 'description' => t('The specific value of the array.'),
  348. 'dynamic' => TRUE,
  349. ];
  350. // Random tokens.
  351. $info['types']['random'] = [
  352. 'name' => t('Random'),
  353. 'description' => t('Tokens related to random data.'),
  354. ];
  355. $info['tokens']['random']['number'] = [
  356. 'name' => t('Number'),
  357. 'description' => t('A random number from 0 to @max.', ['@max' => mt_getrandmax()]),
  358. ];
  359. $info['tokens']['random']['hash'] = [
  360. 'name' => t('Hash'),
  361. 'description' => t('A random hash. The possible hashing algorithms are: @hash-algos.', ['@hash-algos' => implode(', ', hash_algos())]),
  362. 'dynamic' => TRUE,
  363. ];
  364. // Define image_with_image_style token type.
  365. if (\Drupal::moduleHandler()->moduleExists('image')) {
  366. $info['types']['image_with_image_style'] = [
  367. 'name' => t('Image with image style'),
  368. 'needs-data' => 'image_with_image_style',
  369. 'module' => 'token',
  370. 'nested' => TRUE,
  371. ];
  372. // Provide tokens for the ImageStyle attributes.
  373. $info['tokens']['image_with_image_style']['mimetype'] = [
  374. 'name' => t('MIME type'),
  375. 'description' => t('The MIME type (image/png, image/bmp, etc.) of the image.'),
  376. ];
  377. $info['tokens']['image_with_image_style']['filesize'] = [
  378. 'name' => t('File size'),
  379. 'description' => t('The file size of the image.'),
  380. ];
  381. $info['tokens']['image_with_image_style']['height'] = [
  382. 'name' => t('Height'),
  383. 'description' => t('The height the image, in pixels.'),
  384. ];
  385. $info['tokens']['image_with_image_style']['width'] = [
  386. 'name' => t('Width'),
  387. 'description' => t('The width of the image, in pixels.'),
  388. ];
  389. $info['tokens']['image_with_image_style']['uri'] = [
  390. 'name' => t('URI'),
  391. 'description' => t('The URI to the image.'),
  392. ];
  393. $info['tokens']['image_with_image_style']['url'] = [
  394. 'name' => t('URL'),
  395. 'description' => t('The URL to the image.'),
  396. ];
  397. }
  398. return $info;
  399. }
  400. /**
  401. * Implements hook_tokens().
  402. */
  403. function token_tokens($type, array $tokens, array $data = [], array $options = [], BubbleableMetadata $bubbleable_metadata) {
  404. $replacements = [];
  405. $language_manager = \Drupal::languageManager();
  406. $url_options = ['absolute' => TRUE];
  407. if (isset($options['langcode'])) {
  408. $url_options['language'] = $language_manager->getLanguage($options['langcode']);
  409. $langcode = $options['langcode'];
  410. }
  411. else {
  412. $langcode = $language_manager->getCurrentLanguage()->getId();
  413. }
  414. // Date tokens.
  415. if ($type == 'date') {
  416. $date = !empty($data['date']) ? $data['date'] : \Drupal::time()->getRequestTime();
  417. // @todo Remove when http://drupal.org/node/1173706 is fixed.
  418. $date_format_types = \Drupal::entityTypeManager()->getStorage('date_format')->loadMultiple();
  419. foreach ($tokens as $name => $original) {
  420. if (isset($date_format_types[$name]) && _token_module('date', $name) == 'token') {
  421. $replacements[$original] = \Drupal::service('date.formatter')->format($date, $name, '', NULL, $langcode);
  422. }
  423. }
  424. }
  425. // Current date tokens.
  426. // @todo Remove when http://drupal.org/node/943028 is fixed.
  427. if ($type == 'current-date') {
  428. $replacements += \Drupal::token()->generate('date', $tokens, ['date' => \Drupal::time()->getRequestTime()], $options, $bubbleable_metadata);
  429. }
  430. // Comment tokens.
  431. if ($type == 'comment' && !empty($data['comment'])) {
  432. /* @var \Drupal\comment\CommentInterface $comment */
  433. $comment = $data['comment'];
  434. // Chained token relationships.
  435. if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url'))) {
  436. // Add fragment to url options.
  437. $replacements += \Drupal::token()->generate('url', $url_tokens, ['url' => $comment->toUrl('canonical', ['fragment' => "comment-{$comment->id()}"])], $options, $bubbleable_metadata);
  438. }
  439. }
  440. // Node tokens.
  441. if ($type == 'node' && !empty($data['node'])) {
  442. /* @var \Drupal\node\NodeInterface $node */
  443. $node = $data['node'];
  444. foreach ($tokens as $name => $original) {
  445. switch ($name) {
  446. case 'log':
  447. $replacements[$original] = (string) $node->revision_log->value;
  448. break;
  449. case 'content-type':
  450. $type_name = \Drupal::entityTypeManager()->getStorage('node_type')->load($node->getType())->label();
  451. $replacements[$original] = $type_name;
  452. break;
  453. }
  454. }
  455. // Chained token relationships.
  456. if (($parent_tokens = \Drupal::token()->findWithPrefix($tokens, 'source')) && $source_node = $node->getUntranslated()) {
  457. $replacements += \Drupal::token()->generate('node', $parent_tokens, ['node' => $source_node], $options, $bubbleable_metadata);
  458. }
  459. if (($node_type_tokens = \Drupal::token()->findWithPrefix($tokens, 'content-type')) && $node_type = NodeType::load($node->bundle())) {
  460. $replacements += \Drupal::token()->generate('content-type', $node_type_tokens, ['node_type' => $node_type], $options, $bubbleable_metadata);
  461. }
  462. if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url'))) {
  463. $replacements += \Drupal::token()->generate('url', $url_tokens, ['url' => $node->toUrl()], $options, $bubbleable_metadata);
  464. }
  465. }
  466. // Content type tokens.
  467. if ($type == 'content-type' && !empty($data['node_type'])) {
  468. /* @var \Drupal\node\NodeTypeInterface $node_type */
  469. $node_type = $data['node_type'];
  470. foreach ($tokens as $name => $original) {
  471. switch ($name) {
  472. case 'name':
  473. $replacements[$original] = $node_type->label();
  474. break;
  475. case 'machine-name':
  476. $replacements[$original] = $node_type->id();
  477. break;
  478. case 'description':
  479. $replacements[$original] = $node_type->getDescription();
  480. break;
  481. case 'node-count':
  482. $count = \Drupal::entityQueryAggregate('node')
  483. ->aggregate('nid', 'COUNT')
  484. ->condition('type', $node_type->id())
  485. ->execute();
  486. $replacements[$original] = (int) $count;
  487. break;
  488. case 'edit-url':
  489. $replacements[$original] = $node_type->toUrl('edit-form', $url_options)->toString();
  490. break;
  491. }
  492. }
  493. }
  494. // Taxonomy term tokens.
  495. if ($type == 'term' && !empty($data['term'])) {
  496. /* @var \Drupal\taxonomy\TermInterface $term */
  497. $term = $data['term'];
  498. /** @var \Drupal\taxonomy\TermStorageInterface $term_storage */
  499. $term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
  500. foreach ($tokens as $name => $original) {
  501. switch ($name) {
  502. case 'edit-url':
  503. $replacements[$original] = Url::fromRoute('entity.taxonomy_term.edit_form', ['taxonomy_term' => $term->id()], $url_options)->toString();
  504. break;
  505. case 'parents':
  506. if ($parents = token_taxonomy_term_load_all_parents($term->id(), $langcode)) {
  507. $replacements[$original] = token_render_array($parents, $options);
  508. }
  509. break;
  510. case 'root':
  511. $parents = $term_storage->loadAllParents($term->id());
  512. $root_term = end($parents);
  513. if ($root_term->id() != $term->id()) {
  514. $root_term = \Drupal::service('entity.repository')->getTranslationFromContext($root_term, $langcode);
  515. $replacements[$original] = $root_term->label();
  516. }
  517. break;
  518. }
  519. }
  520. // Chained token relationships.
  521. if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url'))) {
  522. $replacements += \Drupal::token()->generate('url', $url_tokens, ['url' => $term->toUrl()], $options, $bubbleable_metadata);
  523. }
  524. // [term:parents:*] chained tokens.
  525. if ($parents_tokens = \Drupal::token()->findWithPrefix($tokens, 'parents')) {
  526. if ($parents = token_taxonomy_term_load_all_parents($term->id(), $langcode)) {
  527. $replacements += \Drupal::token()->generate('array', $parents_tokens, ['array' => $parents], $options, $bubbleable_metadata);
  528. }
  529. }
  530. if ($root_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) {
  531. $parents = $term_storage->loadAllParents($term->id());
  532. $root_term = end($parents);
  533. if ($root_term->tid != $term->id()) {
  534. $replacements += \Drupal::token()->generate('term', $root_tokens, ['term' => $root_term], $options, $bubbleable_metadata);
  535. }
  536. }
  537. }
  538. // Vocabulary tokens.
  539. if ($type == 'vocabulary' && !empty($data['vocabulary'])) {
  540. $vocabulary = $data['vocabulary'];
  541. foreach ($tokens as $name => $original) {
  542. switch ($name) {
  543. case 'machine-name':
  544. $replacements[$original] = $vocabulary->id();
  545. break;
  546. case 'edit-url':
  547. $replacements[$original] = Url::fromRoute('entity.taxonomy_vocabulary.edit_form', ['taxonomy_vocabulary' => $vocabulary->id()], $url_options)->toString();
  548. break;
  549. }
  550. }
  551. }
  552. // File tokens.
  553. if ($type == 'file' && !empty($data['file'])) {
  554. $file = $data['file'];
  555. foreach ($tokens as $name => $original) {
  556. switch ($name) {
  557. case 'basename':
  558. $basename = pathinfo($file->uri->value, PATHINFO_BASENAME);
  559. $replacements[$original] = $basename;
  560. break;
  561. case 'extension':
  562. $extension = pathinfo($file->uri->value, PATHINFO_EXTENSION);
  563. $replacements[$original] = $extension;
  564. break;
  565. case 'size-raw':
  566. $replacements[$original] = (int) $file->filesize->value;
  567. break;
  568. }
  569. }
  570. }
  571. // User tokens.
  572. if ($type == 'user' && !empty($data['user'])) {
  573. /* @var \Drupal\user\UserInterface $account */
  574. $account = $data['user'];
  575. foreach ($tokens as $name => $original) {
  576. switch ($name) {
  577. case 'picture':
  578. if ($account instanceof UserInterface && $account->hasField('user_picture')) {
  579. /** @var \Drupal\Core\Render\RendererInterface $renderer */
  580. $renderer = \Drupal::service('renderer');
  581. $output = [
  582. '#theme' => 'user_picture',
  583. '#account' => $account,
  584. ];
  585. $replacements[$original] = $renderer->renderPlain($output);
  586. }
  587. break;
  588. case 'roles':
  589. $roles = $account->getRoles();
  590. $roles_names = array_combine($roles, $roles);
  591. $replacements[$original] = token_render_array($roles_names, $options);
  592. break;
  593. }
  594. }
  595. // Chained token relationships.
  596. if ($account instanceof UserInterface && $account->hasField('user_picture') && ($picture_tokens = \Drupal::token()->findWithPrefix($tokens, 'picture'))) {
  597. $replacements += \Drupal::token()->generate('file', $picture_tokens, ['file' => $account->user_picture->entity], $options, $bubbleable_metadata);
  598. }
  599. if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
  600. $replacements += \Drupal::token()->generate('url', $url_tokens, ['url' => $account->toUrl()], $options, $bubbleable_metadata);
  601. }
  602. if ($role_tokens = \Drupal::token()->findWithPrefix($tokens, 'roles')) {
  603. $roles = $account->getRoles();
  604. $roles_names = array_combine($roles, $roles);
  605. $replacements += \Drupal::token()->generate('array', $role_tokens, ['array' => $roles_names], $options, $bubbleable_metadata);
  606. }
  607. }
  608. // Current user tokens.
  609. if ($type == 'current-user') {
  610. foreach ($tokens as $name => $original) {
  611. switch ($name) {
  612. case 'ip-address':
  613. $ip = \Drupal::request()->getClientIp();
  614. $replacements[$original] = $ip;
  615. break;
  616. }
  617. }
  618. }
  619. // Menu link tokens.
  620. if ($type == 'menu-link' && !empty($data['menu-link'])) {
  621. /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
  622. $link = $data['menu-link'];
  623. /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
  624. $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
  625. if ($link instanceof MenuLinkContentInterface) {
  626. $link = $menu_link_manager->createInstance($link->getPluginId());
  627. }
  628. foreach ($tokens as $name => $original) {
  629. switch ($name) {
  630. case 'id':
  631. $replacements[$original] = $link->getPluginId();
  632. break;
  633. case 'title':
  634. $replacements[$original] = token_menu_link_translated_title($link, $langcode);
  635. break;
  636. case 'url':
  637. $replacements[$original] = $link->getUrlObject()->setAbsolute()->toString();
  638. break;
  639. case 'parent':
  640. /** @var \Drupal\Core\Menu\MenuLinkInterface $parent */
  641. if ($link->getParent() && $parent = $menu_link_manager->createInstance($link->getParent())) {
  642. $replacements[$original] = token_menu_link_translated_title($parent, $langcode);
  643. }
  644. break;
  645. case 'parents':
  646. if ($parents = token_menu_link_load_all_parents($link->getPluginId(), $langcode)) {
  647. $replacements[$original] = token_render_array($parents, $options);
  648. }
  649. break;
  650. case 'root';
  651. if ($link->getParent() && $parent_ids = array_keys(token_menu_link_load_all_parents($link->getPluginId(), $langcode))) {
  652. $root = $menu_link_manager->createInstance(array_shift($parent_ids));
  653. $replacements[$original] = token_menu_link_translated_title($root, $langcode);
  654. }
  655. break;
  656. }
  657. }
  658. // Chained token relationships.
  659. /** @var \Drupal\Core\Menu\MenuLinkInterface $parent */
  660. if ($link->getParent() && ($parent_tokens = \Drupal::token()->findWithPrefix($tokens, 'parent')) && $parent = $menu_link_manager->createInstance($link->getParent())) {
  661. $replacements += \Drupal::token()->generate('menu-link', $parent_tokens, ['menu-link' => $parent], $options, $bubbleable_metadata);
  662. }
  663. // [menu-link:parents:*] chained tokens.
  664. if ($parents_tokens = \Drupal::token()->findWithPrefix($tokens, 'parents')) {
  665. if ($parents = token_menu_link_load_all_parents($link->getPluginId(), $langcode)) {
  666. $replacements += \Drupal::token()->generate('array', $parents_tokens, ['array' => $parents], $options, $bubbleable_metadata);
  667. }
  668. }
  669. if (($root_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) && $link->getParent() && $parent_ids = array_keys(token_menu_link_load_all_parents($link->getPluginId(), $langcode))) {
  670. $root = $menu_link_manager->createInstance(array_shift($parent_ids));
  671. $replacements += \Drupal::token()->generate('menu-link', $root_tokens, ['menu-link' => $root], $options, $bubbleable_metadata);
  672. }
  673. if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
  674. $replacements += \Drupal::token()->generate('url', $url_tokens, ['url' => $link->getUrlObject()], $options, $bubbleable_metadata);
  675. }
  676. }
  677. // Current page tokens.
  678. if ($type == 'current-page') {
  679. $request = \Drupal::request();
  680. foreach ($tokens as $name => $original) {
  681. switch ($name) {
  682. case 'title':
  683. $route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT);
  684. if ($route) {
  685. $title = \Drupal::service('title_resolver')->getTitle($request, $route);
  686. $replacements[$original] = token_render_array_value($title);
  687. }
  688. break;
  689. case 'url':
  690. $bubbleable_metadata->addCacheContexts(['url.path']);
  691. try {
  692. $url = Url::createFromRequest($request)->setOptions($url_options);
  693. }
  694. catch (\Exception $e) {
  695. // Url::createFromRequest() can fail, e.g. on 404 pages.
  696. // Fall back and try again with Url::fromUserInput().
  697. try {
  698. $url = Url::fromUserInput($request->getPathInfo(), $url_options);
  699. }
  700. catch (\Exception $e) {
  701. // Instantiation would fail again on malformed urls.
  702. }
  703. }
  704. if (isset($url)) {
  705. $replacements[$original] = $url->toString();
  706. }
  707. break;
  708. case 'page-number':
  709. if ($page = $request->query->get('page')) {
  710. // @see PagerDefault::execute()
  711. $pager_page_array = explode(',', $page);
  712. $page = $pager_page_array[0];
  713. }
  714. $replacements[$original] = (int) $page + 1;
  715. break;
  716. }
  717. }
  718. // @deprecated
  719. // [current-page:arg] dynamic tokens.
  720. if ($arg_tokens = \Drupal::token()->findWithPrefix($tokens, 'arg')) {
  721. $path = ltrim(\Drupal::service('path.current')->getPath(), '/');
  722. // Make sure its a system path.
  723. $path = \Drupal::service('path.alias_manager')->getPathByAlias($path);
  724. foreach ($arg_tokens as $name => $original) {
  725. $parts = explode('/', $path);
  726. if (is_numeric($name) && isset($parts[$name])) {
  727. $replacements[$original] = $parts[$name];
  728. }
  729. }
  730. }
  731. // [current-page:query] dynamic tokens.
  732. if ($query_tokens = \Drupal::token()->findWithPrefix($tokens, 'query')) {
  733. $bubbleable_metadata->addCacheContexts(['url.query_args']);
  734. foreach ($query_tokens as $name => $original) {
  735. if (\Drupal::request()->query->has($name)) {
  736. $value = \Drupal::request()->query->get($name);
  737. $replacements[$original] = $value;
  738. }
  739. }
  740. }
  741. // Chained token relationships.
  742. if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
  743. $url = NULL;
  744. try {
  745. $url = Url::createFromRequest($request)->setOptions($url_options);
  746. }
  747. catch (\Exception $e) {
  748. // Url::createFromRequest() can fail, e.g. on 404 pages.
  749. // Fall back and try again with Url::fromUserInput().
  750. try {
  751. $url = Url::fromUserInput($request->getPathInfo(), $url_options);
  752. }
  753. catch (\Exception $e) {
  754. // Instantiation would fail again on malformed urls.
  755. }
  756. }
  757. // Add cache contexts to ensure this token functions on a per-path basis
  758. $bubbleable_metadata->addCacheContexts(['url.path']);
  759. $replacements += \Drupal::token()->generate('url', $url_tokens, ['url' => $url], $options, $bubbleable_metadata);
  760. }
  761. }
  762. // URL tokens.
  763. if ($type == 'url' && !empty($data['url'])) {
  764. /** @var \Drupal\Core\Url $url */
  765. $url = $data['url'];
  766. // To retrieve the correct path, modify a copy of the Url object.
  767. $path_url = clone $url;
  768. $path = '/';
  769. // Ensure the URL is routed to avoid throwing an exception.
  770. if ($url->isRouted()) {
  771. $path .= $path_url->setAbsolute(FALSE)->setOption('fragment', NULL)->getInternalPath();
  772. }
  773. foreach ($tokens as $name => $original) {
  774. switch ($name) {
  775. case 'path':
  776. $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode) : $path;
  777. $replacements[$original] = $value;
  778. break;
  779. case 'alias':
  780. // @deprecated
  781. $alias = \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode);
  782. $replacements[$original] = $alias;
  783. break;
  784. case 'absolute':
  785. $replacements[$original] = $url->setAbsolute()->toString();
  786. break;
  787. case 'relative':
  788. $replacements[$original] = $url->setAbsolute(FALSE)->toString();
  789. break;
  790. case 'brief':
  791. $replacements[$original] = preg_replace(['!^https?://!', '!/$!'], '', $url->setAbsolute()->toString());
  792. break;
  793. case 'unaliased':
  794. $unaliased = clone $url;
  795. $replacements[$original] = $unaliased->setAbsolute()->setOption('alias', TRUE)->toString();
  796. break;
  797. case 'args':
  798. $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode) : $path;
  799. $replacements[$original] = token_render_array(explode('/', $value), $options);
  800. break;
  801. }
  802. }
  803. // [url:args:*] chained tokens.
  804. if ($arg_tokens = \Drupal::token()->findWithPrefix($tokens, 'args')) {
  805. $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode) : $path;
  806. $replacements += \Drupal::token()->generate('array', $arg_tokens, ['array' => explode('/', ltrim($value, '/'))], $options, $bubbleable_metadata);
  807. }
  808. // [url:unaliased:*] chained tokens.
  809. if ($unaliased_tokens = \Drupal::token()->findWithPrefix($tokens, 'unaliased')) {
  810. $url->setOption('alias', TRUE);
  811. $replacements += \Drupal::token()->generate('url', $unaliased_tokens, ['url' => $url], $options, $bubbleable_metadata);
  812. }
  813. }
  814. // Entity tokens.
  815. if (!empty($data[$type]) && $entity_type = \Drupal::service('token.entity_mapper')->getEntityTypeForTokenType($type)) {
  816. /* @var \Drupal\Core\Entity\EntityInterface $entity */
  817. $entity = $data[$type];
  818. foreach ($tokens as $name => $original) {
  819. switch ($name) {
  820. case 'url':
  821. if (_token_module($type, 'url') === 'token' && !$entity->isNew() && $entity->hasLinkTemplate('canonical')) {
  822. $replacements[$original] = $entity->toUrl('canonical')->toString();
  823. }
  824. break;
  825. case 'original':
  826. if (_token_module($type, 'original') == 'token' && !empty($entity->original)) {
  827. $label = $entity->original->label();
  828. $replacements[$original] = $label;
  829. }
  830. break;
  831. }
  832. }
  833. // [entity:url:*] chained tokens.
  834. if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) && _token_module($type, 'url') == 'token') {
  835. $replacements += \Drupal::token()->generate('url', $url_tokens, ['url' => $entity->toUrl()], $options, $bubbleable_metadata);
  836. }
  837. // [entity:original:*] chained tokens.
  838. if (($original_tokens = \Drupal::token()->findWithPrefix($tokens, 'original')) && _token_module($type, 'original') == 'token' && !empty($entity->original)) {
  839. $replacements += \Drupal::token()->generate($type, $original_tokens, [$type => $entity->original], $options, $bubbleable_metadata);
  840. }
  841. // Pass through to an generic 'entity' token type generation.
  842. $entity_data = [
  843. 'entity_type' => $entity_type,
  844. 'entity' => $entity,
  845. 'token_type' => $type,
  846. ];
  847. // @todo Investigate passing through more data like everything from entity_extract_ids().
  848. $replacements += \Drupal::token()->generate('entity', $tokens, $entity_data, $options, $bubbleable_metadata);
  849. }
  850. // Array tokens.
  851. if ($type == 'array' && !empty($data['array']) && is_array($data['array'])) {
  852. $array = $data['array'];
  853. $sort = isset($options['array sort']) ? $options['array sort'] : TRUE;
  854. $keys = token_element_children($array, $sort);
  855. /** @var \Drupal\Core\Render\RendererInterface $renderer */
  856. $renderer = \Drupal::service('renderer');
  857. foreach ($tokens as $name => $original) {
  858. switch ($name) {
  859. case 'first':
  860. $value = $array[$keys[0]];
  861. $value = is_array($value) ? $renderer->renderPlain($value) : (string) $value;
  862. $replacements[$original] = $value;
  863. break;
  864. case 'last':
  865. $value = $array[$keys[count($keys) - 1]];
  866. $value = is_array($value) ? $renderer->renderPlain($value) : (string) $value;
  867. $replacements[$original] = $value;
  868. break;
  869. case 'count':
  870. $replacements[$original] = count($keys);
  871. break;
  872. case 'keys':
  873. $replacements[$original] = token_render_array($keys, $options);
  874. break;
  875. case 'reversed':
  876. $reversed = array_reverse($array, TRUE);
  877. $replacements[$original] = token_render_array($reversed, $options);
  878. break;
  879. case 'join':
  880. $replacements[$original] = token_render_array($array, ['join' => ''] + $options);
  881. break;
  882. }
  883. }
  884. // [array:value:*] dynamic tokens.
  885. if ($value_tokens = \Drupal::token()->findWithPrefix($tokens, 'value')) {
  886. foreach ($value_tokens as $key => $original) {
  887. if ($key[0] !== '#' && isset($array[$key])) {
  888. $replacements[$original] = token_render_array_value($array[$key], $options);
  889. }
  890. }
  891. }
  892. // [array:join:*] dynamic tokens.
  893. if ($join_tokens = \Drupal::token()->findWithPrefix($tokens, 'join')) {
  894. foreach ($join_tokens as $join => $original) {
  895. $replacements[$original] = token_render_array($array, ['join' => $join] + $options);
  896. }
  897. }
  898. // [array:keys:*] chained tokens.
  899. if ($key_tokens = \Drupal::token()->findWithPrefix($tokens, 'keys')) {
  900. $replacements += \Drupal::token()->generate('array', $key_tokens, ['array' => $keys], $options, $bubbleable_metadata);
  901. }
  902. // [array:reversed:*] chained tokens.
  903. if ($reversed_tokens = \Drupal::token()->findWithPrefix($tokens, 'reversed')) {
  904. $replacements += \Drupal::token()->generate('array', $reversed_tokens, ['array' => array_reverse($array, TRUE)], ['array sort' => FALSE] + $options, $bubbleable_metadata);
  905. }
  906. // @todo Handle if the array values are not strings and could be chained.
  907. }
  908. // Random tokens.
  909. if ($type == 'random') {
  910. foreach ($tokens as $name => $original) {
  911. switch ($name) {
  912. case 'number':
  913. $replacements[$original] = mt_rand();
  914. break;
  915. }
  916. }
  917. // [custom:hash:*] dynamic token.
  918. if ($hash_tokens = \Drupal::token()->findWithPrefix($tokens, 'hash')) {
  919. $algos = hash_algos();
  920. foreach ($hash_tokens as $name => $original) {
  921. if (in_array($name, $algos)) {
  922. $replacements[$original] = hash($name, Crypt::randomBytes(55));
  923. }
  924. }
  925. }
  926. }
  927. // If $type is a token type, $data[$type] is empty but $data[$entity_type] is
  928. // not, re-run token replacements.
  929. if (empty($data[$type]) && ($entity_type = \Drupal::service('token.entity_mapper')->getEntityTypeForTokenType($type)) && $entity_type != $type && !empty($data[$entity_type]) && empty($options['recursive'])) {
  930. $data[$type] = $data[$entity_type];
  931. $options['recursive'] = TRUE;
  932. $replacements += \Drupal::moduleHandler()->invokeAll('tokens', [$type, $tokens, $data, $options, $bubbleable_metadata]);
  933. }
  934. // If the token type specifics a 'needs-data' value, and the value is not
  935. // present in $data, then throw an error.
  936. if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
  937. // Only check when tests are running.
  938. $type_info = \Drupal::token()->getTypeInfo($type);
  939. if (!empty($type_info['needs-data']) && !isset($data[$type_info['needs-data']])) {
  940. trigger_error(t('Attempting to perform token replacement for token type %type without required data', ['%type' => $type]), E_USER_WARNING);
  941. }
  942. }
  943. return $replacements;
  944. }
  945. /**
  946. * Implements hook_token_info() on behalf of book.module.
  947. */
  948. function book_token_info() {
  949. $info['types']['book'] = [
  950. 'name' => t('Book'),
  951. 'description' => t('Tokens related to books.'),
  952. 'needs-data' => 'book',
  953. ];
  954. $info['tokens']['book']['title'] = [
  955. 'name' => t('Title'),
  956. 'description' => t('Title of the book.'),
  957. ];
  958. $info['tokens']['book']['author'] = [
  959. 'name' => t('Author'),
  960. 'description' => t('The author of the book.'),
  961. 'type' => 'user',
  962. ];
  963. $info['tokens']['book']['root'] = [
  964. 'name' => t('Root'),
  965. 'description' => t('Top level of the book.'),
  966. 'type' => 'node',
  967. ];
  968. $info['tokens']['book']['parent'] = [
  969. 'name' => t('Parent'),
  970. 'description' => t('Parent of the current page.'),
  971. 'type' => 'node',
  972. ];
  973. $info['tokens']['book']['parents'] = [
  974. 'name' => t('Parents'),
  975. 'description' => t("An array of all the node's parents, starting with the root."),
  976. 'type' => 'array',
  977. ];
  978. $info['tokens']['node']['book'] = [
  979. 'name' => t('Book'),
  980. 'description' => t('The book page associated with the node.'),
  981. 'type' => 'book',
  982. ];
  983. return $info;
  984. }
  985. /**
  986. * Implements hook_tokens() on behalf of book.module.
  987. */
  988. function book_tokens($type, $tokens, array $data = [], array $options = [], BubbleableMetadata $bubbleable_metadata) {
  989. $replacements = [];
  990. // Node tokens.
  991. if ($type == 'node' && !empty($data['node'])) {
  992. $book = $data['node']->book;
  993. if (!empty($book['bid'])) {
  994. if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'book')) {
  995. $child_node = Node::load($book['nid']);
  996. $replacements += \Drupal::token()->generate('book', $book_tokens, ['book' => $child_node], $options, $bubbleable_metadata);
  997. }
  998. }
  999. }
  1000. // Book tokens.
  1001. elseif ($type == 'book' && !empty($data['book'])) {
  1002. $book = $data['book']->book;
  1003. if (!empty($book['bid'])) {
  1004. $book_node = Node::load($book['bid']);
  1005. foreach ($tokens as $name => $original) {
  1006. switch ($name) {
  1007. case 'root':
  1008. case 'title':
  1009. $replacements[$original] = $book_node->getTitle();
  1010. break;
  1011. case 'parent':
  1012. if (!empty($book['pid'])) {
  1013. $parent_node = Node::load($book['pid']);
  1014. $replacements[$original] = $parent_node->getTitle();
  1015. }
  1016. break;
  1017. case 'parents':
  1018. if ($parents = token_book_load_all_parents($book)) {
  1019. $replacements[$original] = token_render_array($parents, $options);
  1020. }
  1021. break;
  1022. }
  1023. }
  1024. if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'author')) {
  1025. $replacements += \Drupal::token()->generate('user', $book_tokens, ['user' => $book_node->getOwner()], $options, $bubbleable_metadata);
  1026. }
  1027. if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) {
  1028. $replacements += \Drupal::token()->generate('node', $book_tokens, ['node' => $book_node], $options, $bubbleable_metadata);
  1029. }
  1030. if (!empty($book['pid']) && $book_tokens = \Drupal::token()->findWithPrefix($tokens, 'parent')) {
  1031. $parent_node = Node::load($book['pid']);
  1032. $replacements += \Drupal::token()->generate('node', $book_tokens, ['node' => $parent_node], $options, $bubbleable_metadata);
  1033. }
  1034. if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'parents')) {
  1035. $parents = token_book_load_all_parents($book);
  1036. $replacements += \Drupal::token()->generate('array', $book_tokens, ['array' => $parents], $options, $bubbleable_metadata);
  1037. }
  1038. }
  1039. }
  1040. return $replacements;
  1041. }
  1042. /**
  1043. * Implements hook_token_info() on behalf of menu_ui.module.
  1044. */
  1045. function menu_ui_token_info() {
  1046. // Menu tokens.
  1047. $info['types']['menu'] = [
  1048. 'name' => t('Menus'),
  1049. 'description' => t('Tokens related to menus.'),
  1050. 'needs-data' => 'menu',
  1051. ];
  1052. $info['tokens']['menu']['name'] = [
  1053. 'name' => t('Name'),
  1054. 'description' => t("The name of the menu."),
  1055. ];
  1056. $info['tokens']['menu']['machine-name'] = [
  1057. 'name' => t('Machine-readable name'),
  1058. 'description' => t("The unique machine-readable name of the menu."),
  1059. ];
  1060. $info['tokens']['menu']['description'] = [
  1061. 'name' => t('Description'),
  1062. 'description' => t('The optional description of the menu.'),
  1063. ];
  1064. $info['tokens']['menu']['menu-link-count'] = [
  1065. 'name' => t('Menu link count'),
  1066. 'description' => t('The number of menu links belonging to the menu.'),
  1067. ];
  1068. $info['tokens']['menu']['edit-url'] = [
  1069. 'name' => t('Edit URL'),
  1070. 'description' => t("The URL of the menu's edit page."),
  1071. ];
  1072. $info['tokens']['menu-link']['menu'] = [
  1073. 'name' => t('Menu'),
  1074. 'description' => t('The menu of the menu link.'),
  1075. 'type' => 'menu',
  1076. ];
  1077. $info['tokens']['menu-link']['edit-url'] = [
  1078. 'name' => t('Edit URL'),
  1079. 'description' => t("The URL of the menu link's edit page."),
  1080. ];
  1081. $info['tokens']['node']['menu-link'] = [
  1082. 'name' => t('Menu link'),
  1083. 'description' => t("The menu link for this node."),
  1084. 'type' => 'menu-link',
  1085. ];
  1086. return $info;
  1087. }
  1088. /**
  1089. * Implements hook_tokens() on behalf of menu_ui.module.
  1090. */
  1091. function menu_ui_tokens($type, $tokens, array $data = [], array $options = [], BubbleableMetadata $bubbleable_metadata) {
  1092. $replacements = [];
  1093. /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
  1094. $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
  1095. $url_options = ['absolute' => TRUE];
  1096. if (isset($options['langcode'])) {
  1097. $url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
  1098. $langcode = $options['langcode'];
  1099. }
  1100. else {
  1101. $langcode = NULL;
  1102. }
  1103. // Node tokens.
  1104. if ($type == 'node' && !empty($data['node'])) {
  1105. /** @var \Drupal\node\NodeInterface $node */
  1106. $node = $data['node'];
  1107. foreach ($tokens as $name => $original) {
  1108. switch ($name) {
  1109. case 'menu-link':
  1110. // On node-form save we populate a calculated field with a menu_link
  1111. // references.
  1112. // @see token_node_menu_link_submit()
  1113. if ($node->getFieldDefinition('menu_link') && $menu_link = $node->menu_link->entity) {
  1114. /** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link */
  1115. $replacements[$original] = $menu_link->getTitle();
  1116. }
  1117. else {
  1118. $url = $node->toUrl();
  1119. if ($links = $menu_link_manager->loadLinksByRoute($url->getRouteName(), $url->getRouteParameters())) {
  1120. $link = _token_menu_link_best_match($node, $links);
  1121. $replacements[$original] = token_menu_link_translated_title($link, $langcode);
  1122. }
  1123. }
  1124. break;
  1125. }
  1126. // Chained token relationships.
  1127. if ($menu_tokens = \Drupal::token()->findWithPrefix($tokens, 'menu-link')) {
  1128. if ($node->getFieldDefinition('menu_link') && $menu_link = $node->menu_link->entity) {
  1129. /** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link */
  1130. $replacements += \Drupal::token()->generate('menu-link', $menu_tokens, ['menu-link' => $menu_link], $options, $bubbleable_metadata);
  1131. }
  1132. else {
  1133. $url = $node->toUrl();
  1134. if ($links = $menu_link_manager->loadLinksByRoute($url->getRouteName(), $url->getRouteParameters())) {
  1135. $link = _token_menu_link_best_match($node, $links);
  1136. $replacements += \Drupal::token()->generate('menu-link', $menu_tokens, ['menu-link' => $link], $options, $bubbleable_metadata);
  1137. }
  1138. }
  1139. }
  1140. }
  1141. }
  1142. // Menu link tokens.
  1143. if ($type == 'menu-link' && !empty($data['menu-link'])) {
  1144. /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
  1145. $link = $data['menu-link'];
  1146. if ($link instanceof MenuLinkContentInterface) {
  1147. $link = $menu_link_manager->createInstance($link->getPluginId());
  1148. }
  1149. foreach ($tokens as $name => $original) {
  1150. switch ($name) {
  1151. case 'menu':
  1152. if ($menu = Menu::load($link->getMenuName())) {
  1153. $replacements[$original] = $menu->label();
  1154. }
  1155. break;
  1156. case 'edit-url':
  1157. $replacements[$original] = $link->getEditRoute()->setOptions($url_options)->toString();
  1158. break;
  1159. }
  1160. }
  1161. // Chained token relationships.
  1162. if (($menu_tokens = \Drupal::token()->findWithPrefix($tokens, 'menu')) && $menu = Menu::load($link->getMenuName())) {
  1163. $replacements += \Drupal::token()->generate('menu', $menu_tokens, ['menu' => $menu], $options, $bubbleable_metadata);
  1164. }
  1165. }
  1166. // Menu tokens.
  1167. if ($type == 'menu' && !empty($data['menu'])) {
  1168. /** @var \Drupal\system\MenuInterface $menu */
  1169. $menu = $data['menu'];
  1170. foreach ($tokens as $name => $original) {
  1171. switch ($name) {
  1172. case 'name':
  1173. $replacements[$original] = $menu->label();
  1174. break;
  1175. case 'machine-name':
  1176. $replacements[$original] = $menu->id();
  1177. break;
  1178. case 'description':
  1179. $replacements[$original] = $menu->getDescription();
  1180. break;
  1181. case 'menu-link-count':
  1182. $replacements[$original] = $menu_link_manager->countMenuLinks($menu->id());
  1183. break;
  1184. case 'edit-url':
  1185. $replacements[$original] = Url::fromRoute('entity.menu.edit_form', ['menu' => $menu->id()], $url_options)->toString();
  1186. break;
  1187. }
  1188. }
  1189. }
  1190. return $replacements;
  1191. }
  1192. /**
  1193. * Returns a best matched link for a given node.
  1194. *
  1195. * If the url exists in multiple menus, default to the one set on the node
  1196. * itself.
  1197. *
  1198. * @param \Drupal\node\NodeInterface $node
  1199. * The node to look up the default menu settings from.
  1200. * @param array $links
  1201. * An array of instances keyed by plugin ID.
  1202. *
  1203. * @return \Drupal\Core\Menu\MenuLinkInterface
  1204. * A Link instance.
  1205. */
  1206. function _token_menu_link_best_match(NodeInterface $node, array $links) {
  1207. // Get the menu ui defaults so we can determine what menu was
  1208. // selected for this node. This ensures that if the node was added
  1209. // to the menu via the node UI, we use that as a default. If it
  1210. // was not added via the node UI then grab the first in the
  1211. // retrieved array.
  1212. $defaults = menu_ui_get_menu_link_defaults($node);
  1213. if (isset($defaults['id']) && isset($links[$defaults['id']])) {
  1214. $link = $links[$defaults['id']];
  1215. }
  1216. else {
  1217. $link = reset($links);
  1218. }
  1219. return $link;
  1220. }
  1221. /**
  1222. * Implements hook_token_info_alter() on behalf of field.module.
  1223. *
  1224. * We use hook_token_info_alter() rather than hook_token_info() as other
  1225. * modules may already have defined some field tokens.
  1226. */
  1227. function field_token_info_alter(&$info) {
  1228. $type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinitions();
  1229. // Attach field tokens to their respecitve entity tokens.
  1230. foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
  1231. if (!$entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) {
  1232. continue;
  1233. }
  1234. // Make sure a token type exists for this entity.
  1235. $token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity_type_id);
  1236. if (empty($token_type) || !isset($info['types'][$token_type])) {
  1237. continue;
  1238. }
  1239. $fields = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type_id);
  1240. foreach ($fields as $field_name => $field) {
  1241. /** @var \Drupal\field\FieldStorageConfigInterface $field */
  1242. // Ensure the token implements FieldStorageConfigInterface or is defined
  1243. // in token module.
  1244. $provider = '';
  1245. if (isset($info['types'][$token_type]['module'])) {
  1246. $provider = $info['types'][$token_type]['module'];
  1247. }
  1248. if (!($field instanceof FieldStorageConfigInterface) && $provider != 'token') {
  1249. continue;
  1250. }
  1251. // If a token already exists for this field, then don't add it.
  1252. if (isset($info['tokens'][$token_type][$field_name])) {
  1253. continue;
  1254. }
  1255. if ($token_type == 'comment' && $field_name == 'comment_body') {
  1256. // Core provides the comment field as [comment:body].
  1257. continue;
  1258. }
  1259. // Do not define the token type if the field has no properties.
  1260. if (!$field->getPropertyDefinitions()) {
  1261. continue;
  1262. }
  1263. // Generate a description for the token.
  1264. $labels = _token_field_label($entity_type_id, $field_name);
  1265. $label = array_shift($labels);
  1266. $params['@type'] = $type_info[$field->getType()]['label'];
  1267. if (!empty($labels)) {
  1268. $params['%labels'] = implode(', ', $labels);
  1269. $description = t('@type field. Also known as %labels.', $params);
  1270. }
  1271. else {
  1272. $description = t('@type field.', $params);
  1273. }
  1274. $cardinality = $field->getCardinality();
  1275. $cardinality = ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || $cardinality > 3) ? 3 : $cardinality;
  1276. $field_token_name = $token_type . '-' . $field_name;
  1277. $info['tokens'][$token_type][$field_name] = [
  1278. 'name' => Html::escape($label),
  1279. 'description' => $description,
  1280. 'module' => 'token',
  1281. // For multivalue fields the field token is a list type.
  1282. 'type' => $cardinality > 1 ? "list<$field_token_name>" : $field_token_name,
  1283. ];
  1284. // Field token type.
  1285. $info['types'][$field_token_name] = [
  1286. 'name' => Html::escape($label),
  1287. 'description' => t('@label tokens.', ['@label' => Html::escape($label)]),
  1288. 'needs-data' => $field_token_name,
  1289. 'nested' => TRUE,
  1290. ];
  1291. // Field list token type.
  1292. if ($cardinality > 1) {
  1293. $info['types']["list<$field_token_name>"] = [
  1294. 'name' => t('List of @type values', ['@type' => Html::escape($label)]),
  1295. 'description' => t('Tokens for lists of @type values.', ['@type' => Html::escape($label)]),
  1296. 'needs-data' => "list<$field_token_name>",
  1297. 'nested' => TRUE,
  1298. ];
  1299. }
  1300. // Show a different token for each field delta.
  1301. if ($cardinality > 1) {
  1302. for ($delta = 0; $delta < $cardinality; $delta++) {
  1303. $info['tokens']["list<$field_token_name>"][$delta] = [
  1304. 'name' => t('@type type with delta @delta', ['@type' => Html::escape($label), '@delta' => $delta]),
  1305. 'module' => 'token',
  1306. 'type' => $field_token_name,
  1307. ];
  1308. }
  1309. }
  1310. // Property tokens.
  1311. foreach ($field->getPropertyDefinitions() as $property => $property_definition) {
  1312. if (is_subclass_of($property_definition->getClass(), 'Drupal\Core\TypedData\PrimitiveInterface')) {
  1313. $info['tokens'][$field_token_name][$property] = [
  1314. 'name' => $property_definition->getLabel(),
  1315. 'description' => $property_definition->getDescription(),
  1316. 'module' => 'token',
  1317. ];
  1318. }
  1319. elseif (($property_definition instanceof DataReferenceDefinitionInterface) && ($property_definition->getTargetDefinition() instanceof EntityDataDefinitionInterface)) {
  1320. $referenced_entity_type = $property_definition->getTargetDefinition()->getEntityTypeId();
  1321. $referenced_token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($referenced_entity_type);
  1322. $info['tokens'][$field_token_name][$property] = [
  1323. 'name' => $property_definition->getLabel(),
  1324. 'description' => $property_definition->getDescription(),
  1325. 'module' => 'token',
  1326. 'type' => $referenced_token_type,
  1327. ];
  1328. }
  1329. }
  1330. // Provide image_with_image_style tokens for image fields.
  1331. if ($field->getType() == 'image') {
  1332. $image_styles = image_style_options(FALSE);
  1333. foreach ($image_styles as $style => $description) {
  1334. $info['tokens'][$field_token_name][$style] = [
  1335. 'name' => $description,
  1336. 'description' => t('Represents the image in the given image style.'),
  1337. 'type' => 'image_with_image_style',
  1338. ];
  1339. }
  1340. }
  1341. // Provide format token for datetime fields.
  1342. if ($field->getType() == 'datetime') {
  1343. $info['tokens'][$field_token_name]['date'] = $info['tokens'][$field_token_name]['value'];
  1344. $info['tokens'][$field_token_name]['date']['name'] .= ' ' . t('format');
  1345. $info['tokens'][$field_token_name]['date']['type'] = 'date';
  1346. }
  1347. if ($field->getType() == 'daterange' || $field->getType() == 'date_recur') {
  1348. $info['tokens'][$field_token_name]['start_date'] = $info['tokens'][$field_token_name]['value'];
  1349. $info['tokens'][$field_token_name]['start_date']['name'] .= ' ' . t('format');
  1350. $info['tokens'][$field_token_name]['start_date']['type'] = 'date';
  1351. $info['tokens'][$field_token_name]['end_date'] = $info['tokens'][$field_token_name]['end_value'];
  1352. $info['tokens'][$field_token_name]['end_date']['name'] .= ' ' . t('format');
  1353. $info['tokens'][$field_token_name]['end_date']['type'] = 'date';
  1354. }
  1355. }
  1356. }
  1357. }
  1358. /**
  1359. * Returns the label of a certain field.
  1360. *
  1361. * Therefore it looks up in all bundles to find the most used instance.
  1362. *
  1363. * Based on views_entity_field_label().
  1364. *
  1365. * @todo Resync this method with views_entity_field_label().
  1366. */
  1367. function _token_field_label($entity_type, $field_name) {
  1368. $labels = [];
  1369. // Count the amount of instances per label per field.
  1370. foreach (array_keys(\Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type)) as $bundle) {
  1371. $bundle_instances = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type, $bundle);
  1372. if (isset($bundle_instances[$field_name])) {
  1373. $instance = $bundle_instances[$field_name];
  1374. $label = (string) $instance->getLabel();
  1375. $labels[$label] = isset($labels[$label]) ? ++$labels[$label] : 1;
  1376. }
  1377. }
  1378. if (empty($labels)) {
  1379. return [$field_name];
  1380. }
  1381. // Sort the field labels by it most used label and return the labels.
  1382. arsort($labels);
  1383. return array_keys($labels);
  1384. }
  1385. /**
  1386. * Implements hook_tokens() on behalf of field.module.
  1387. */
  1388. function field_tokens($type, $tokens, array $data = [], array $options = [], BubbleableMetadata $bubbleable_metadata) {
  1389. $replacements = [];
  1390. $langcode = isset($options['langcode']) ? $options['langcode'] : NULL;
  1391. // Entity tokens.
  1392. if ($type == 'entity' && !empty($data['entity_type']) && !empty($data['entity']) && !empty($data['token_type'])) {
  1393. /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */
  1394. $entity = $data['entity'];
  1395. if (!($entity instanceof ContentEntityInterface)) {
  1396. return $replacements;
  1397. }
  1398. if (!isset($options['langcode'])) {
  1399. // Set the active language in $options, so that it is passed along.
  1400. $langcode = $options['langcode'] = $entity->language()->getId();
  1401. }
  1402. // Obtain the entity with the correct language.
  1403. $entity = \Drupal::service('entity.repository')->getTranslationFromContext($entity, $langcode);
  1404. $view_mode_name = $entity->getEntityTypeId() . '.' . $entity->bundle() . '.token';
  1405. $view_display = \Drupal::entityTypeManager()->getStorage('entity_view_display')->load($view_mode_name);
  1406. $token_view_display = (!empty($view_display) && $view_display->status());
  1407. foreach ($tokens as $name => $original) {
  1408. // For the [entity:field_name] token.
  1409. if (strpos($name, ':') === FALSE) {
  1410. $field_name = $name;
  1411. $token_name = $name;
  1412. }
  1413. // For [entity:field_name:0], [entity:field_name:0:value] and
  1414. // [entity:field_name:value] tokens.
  1415. else {
  1416. list($field_name, $delta) = explode(':', $name, 2);
  1417. if (!is_numeric($delta)) {
  1418. unset($delta);
  1419. }
  1420. $token_name = $field_name;
  1421. }
  1422. // Ensure the entity has the requested field and that the token for it is
  1423. // defined by token.module.
  1424. if (!$entity->hasField($field_name) || _token_module($data['token_type'], $token_name) != 'token') {
  1425. continue;
  1426. }
  1427. $display_options = 'token';
  1428. // Do not continue if the field is empty.
  1429. if ($entity->get($field_name)->isEmpty()) {
  1430. continue;
  1431. }
  1432. // Handle [entity:field_name] and [entity:field_name:0] tokens.
  1433. if ($field_name === $name || isset($delta)) {
  1434. if (!$token_view_display) {
  1435. // We don't have the token view display and should fall back on
  1436. // default formatters. If the field has specified a specific formatter
  1437. // to be used by default with tokens, use that, otherwise use the
  1438. // default formatter.
  1439. /** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
  1440. $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
  1441. $field_type_definition = $field_type_manager->getDefinition($entity->getFieldDefinition($field_name)->getType());
  1442. $display_options = [
  1443. 'type' => !empty($field_type_definition['default_token_formatter']) ? $field_type_definition['default_token_formatter'] : $field_type_definition['default_formatter'],
  1444. 'label' => 'hidden',
  1445. ];
  1446. }
  1447. // Render only one delta.
  1448. if (isset($delta)) {
  1449. if ($field_delta = $entity->{$field_name}[$delta]) {
  1450. $field_output = $field_delta->view($display_options);
  1451. }
  1452. // If no such delta exists, let's not replace the token.
  1453. else {
  1454. continue;
  1455. }
  1456. }
  1457. // Render the whole field (with all deltas).
  1458. else {
  1459. $field_output = $entity->$field_name->view($display_options);
  1460. // If we are displaying all field items we need this #pre_render
  1461. // callback.
  1462. $field_output['#pre_render'][] = 'token_pre_render_field_token';
  1463. }
  1464. $field_output['#token_options'] = $options;
  1465. $replacements[$original] = \Drupal::service('renderer')->renderPlain($field_output);
  1466. }
  1467. // Handle [entity:field_name:value] and [entity:field_name:0:value]
  1468. // tokens.
  1469. elseif ($field_tokens = \Drupal::token()->findWithPrefix($tokens, $field_name)) {
  1470. $property_token_data = [
  1471. 'field_property' => TRUE,
  1472. $data['entity_type'] . '-' . $field_name => $entity->$field_name,
  1473. 'field_name' => $data['entity_type'] . '-' . $field_name,
  1474. ];
  1475. $replacements += \Drupal::token()->generate($field_name, $field_tokens, $property_token_data, $options, $bubbleable_metadata);
  1476. }
  1477. }
  1478. // Remove the cloned object from memory.
  1479. unset($entity);
  1480. }
  1481. elseif (!empty($data['field_property'])) {
  1482. foreach ($tokens as $token => $original) {
  1483. $filtered_tokens = $tokens;
  1484. $delta = 0;
  1485. $parts = explode(':', $token);
  1486. if (is_numeric($parts[0])) {
  1487. if (count($parts) > 1) {
  1488. $delta = $parts[0];
  1489. $property_name = $parts[1];
  1490. // Pre-filter the tokens to select those with the correct delta.
  1491. $filtered_tokens = \Drupal::token()->findWithPrefix($tokens, $delta);
  1492. // Remove the delta to unify between having and not having one.
  1493. array_shift($parts);
  1494. }
  1495. else {
  1496. // Token is fieldname:delta, which is invalid.
  1497. continue;
  1498. }
  1499. }
  1500. else {
  1501. $property_name = $parts[0];
  1502. }
  1503. if (isset($data[$data['field_name']][$delta])) {
  1504. $field_item = $data[$data['field_name']][$delta];
  1505. }
  1506. else {
  1507. // The field has no such delta, abort replacement.
  1508. continue;
  1509. }
  1510. if (isset($field_item->$property_name) && ($field_item->$property_name instanceof FieldableEntityInterface)) {
  1511. // Entity reference field.
  1512. $entity = $field_item->$property_name;
  1513. // Obtain the referenced entity with the correct language.
  1514. $entity = \Drupal::service('entity.repository')->getTranslationFromContext($entity, $langcode);
  1515. if (count($parts) > 1) {
  1516. $field_tokens = \Drupal::token()->findWithPrefix($filtered_tokens, $property_name);
  1517. $token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity->getEntityTypeId(), TRUE);
  1518. $replacements += \Drupal::token()->generate($token_type, $field_tokens, [$token_type => $entity], $options, $bubbleable_metadata);
  1519. }
  1520. else {
  1521. $replacements[$original] = $entity->label();
  1522. }
  1523. }
  1524. elseif (($field_item->getFieldDefinition()->getType() == 'image') && ($style = ImageStyle::load($property_name))) {
  1525. // Handle [node:field_name:image_style:property] tokens and multivalued
  1526. // [node:field_name:delta:image_style:property] tokens. If the token is
  1527. // of the form [node:field_name:image_style], provide the URL as a
  1528. // replacement.
  1529. $property_name = isset($parts[1]) ? $parts[1] : 'url';
  1530. $entity = $field_item->entity;
  1531. if (!empty($field_item->entity)) {
  1532. $original_uri = $entity->getFileUri();
  1533. // Only generate the image derivative if needed.
  1534. if ($property_name === 'width' || $property_name === 'height') {
  1535. $dimensions = [
  1536. 'width' => $field_item->width,
  1537. 'height' => $field_item->height,
  1538. ];
  1539. $style->transformDimensions($dimensions, $original_uri);
  1540. $replacements[$original] = $dimensions[$property_name];
  1541. }
  1542. elseif ($property_name === 'uri') {
  1543. $replacements[$original] = $style->buildUri($original_uri);
  1544. }
  1545. elseif ($property_name === 'url') {
  1546. $replacements[$original] = $style->buildUrl($original_uri);
  1547. }
  1548. else {
  1549. // Generate the image derivative, if it doesn't already exist.
  1550. $derivative_uri = $style->buildUri($original_uri);
  1551. $derivative_exists = TRUE;
  1552. if (!file_exists($derivative_uri)) {
  1553. $derivative_exists = $style->createDerivative($original_uri, $derivative_uri);
  1554. }
  1555. if ($derivative_exists) {
  1556. $image = \Drupal::service('image.factory')->get($derivative_uri);
  1557. // Provide the replacement.
  1558. switch ($property_name) {
  1559. case 'mimetype':
  1560. $replacements[$original] = $image->getMimeType();
  1561. break;
  1562. case 'filesize':
  1563. $replacements[$original] = $image->getFileSize();
  1564. break;
  1565. }
  1566. }
  1567. }
  1568. }
  1569. }
  1570. elseif (in_array($field_item->getFieldDefinition()->getType(), ['datetime', 'daterange', 'date_recur']) && in_array($property_name, ['date', 'start_date', 'end_date']) && !empty($field_item->$property_name)) {
  1571. $timestamp = $field_item->{$property_name}->getTimestamp();
  1572. // If the token is an exact match for the property or the delta and the
  1573. // property, use the timestamp as-is.
  1574. if ($property_name == $token || "$delta:$property_name" == $token) {
  1575. $replacements[$original] = $timestamp;
  1576. }
  1577. else {
  1578. $date_tokens = \Drupal::token()->findWithPrefix($filtered_tokens, $property_name);
  1579. $replacements += \Drupal::token()->generate('date', $date_tokens, ['date' => $timestamp], $options, $bubbleable_metadata);
  1580. }
  1581. }
  1582. else {
  1583. $replacements[$original] = $field_item->$property_name;
  1584. }
  1585. }
  1586. }
  1587. return $replacements;
  1588. }
  1589. /**
  1590. * Pre-render callback for field output used with tokens.
  1591. */
  1592. function token_pre_render_field_token($elements) {
  1593. // Remove the field theme hook, attachments, and JavaScript states.
  1594. unset($elements['#theme']);
  1595. unset($elements['#states']);
  1596. unset($elements['#attached']);
  1597. // Prevent multi-value fields from appearing smooshed together by appending
  1598. // a join suffix to all but the last value.
  1599. $deltas = Element::getVisibleChildren($elements);
  1600. $count = count($deltas);
  1601. if ($count > 1) {
  1602. $join = isset($elements['#token_options']['join']) ? $elements['#token_options']['join'] : ", ";
  1603. foreach ($deltas as $index => $delta) {
  1604. // Do not add a suffix to the last item.
  1605. if ($index < ($count - 1)) {
  1606. $elements[$delta] += ['#suffix' => $join];
  1607. }
  1608. }
  1609. }
  1610. return $elements;
  1611. }