token.tokens.inc 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725
  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\Unicode;
  13. use Drupal\Component\Utility\Html;
  14. use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
  15. use Drupal\Core\Url;
  16. use Drupal\field\FieldStorageConfigInterface;
  17. use Drupal\menu_link_content\MenuLinkContentInterface;
  18. use Drupal\node\Entity\Node;
  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'] = array(
  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'] = array(
  82. 'name' => t('URL'),
  83. 'description' => t('The URL of the @entity.', array('@entity' => Unicode::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'] = array(
  91. 'name' => t('Original @entity', array('@entity' => Unicode::strtolower($entity_info->getLabel()))),
  92. 'description' => t('The original @entity data if the @entity is being updated or saved.', array('@entity' => Unicode::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] = array(
  105. 'name' => Html::escape($date_format_type_info->label()),
  106. 'description' => t("A date in '@type' format. (%date)", array('@type' => $date_format_type, '%date' => \Drupal::service('date.formatter')->format(REQUEST_TIME, $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'] = array(
  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'] = array(
  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'] = array(
  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'] = array(
  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'] = array(
  138. 'name' => t('Name'),
  139. 'description' => t('The name of the content type.'),
  140. );
  141. $info['tokens']['content-type']['machine-name'] = array(
  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'] = array(
  146. 'name' => t('Description'),
  147. 'description' => t('The optional description of the content type.'),
  148. );
  149. $info['tokens']['content-type']['node-count'] = array(
  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'] = array(
  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'] = array(
  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'] = array(
  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'] = array(
  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'] = array(
  176. 'name' => t('Machine-readable name'),
  177. 'description' => t('The unique machine-readable name of the vocabulary.'),
  178. );
  179. $info['tokens']['vocabulary']['edit-url'] = array(
  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'] = array(
  187. 'name' => t('Base name'),
  188. 'description' => t('The base name of the file.'),
  189. );
  190. $info['tokens']['file']['extension'] = array(
  191. 'name' => t('Extension'),
  192. 'description' => t('The extension of the file.'),
  193. );
  194. $info['tokens']['file']['size-raw'] = array(
  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'] = array(
  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'] = array(
  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'] = array(
  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'] = array(
  219. 'name' => t('IP address'),
  220. 'description' => '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'] = array(
  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'] = array(
  229. 'name' => t('Link ID'),
  230. 'description' => t('The unique ID of the menu link.'),
  231. );
  232. $info['tokens']['menu-link']['title'] = array(
  233. 'name' => t('Title'),
  234. 'description' => t('The title of the menu link.'),
  235. );
  236. $info['tokens']['menu-link']['url'] = array(
  237. 'name' => t('URL'),
  238. 'description' => t('The URL of the menu link.'),
  239. 'type' => 'url',
  240. );
  241. $info['tokens']['menu-link']['parent'] = array(
  242. 'name' => t('Parent'),
  243. 'description' => t("The menu link's parent."),
  244. 'type' => 'menu-link',
  245. );
  246. $info['tokens']['menu-link']['parents'] = array(
  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'] = array(
  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'] = array(
  258. 'name' => t('Current page'),
  259. 'description' => t('Tokens related to the current page request.'),
  260. );
  261. $info['tokens']['current-page']['title'] = array(
  262. 'name' => t('Title'),
  263. 'description' => t('The title of the current page.'),
  264. );
  265. $info['tokens']['current-page']['url'] = array(
  266. 'name' => t('URL'),
  267. 'description' => t('The URL of the current page.'),
  268. 'type' => 'url',
  269. );
  270. $info['tokens']['current-page']['page-number'] = array(
  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'] = array(
  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'] = array(
  281. 'name' => t('URL'),
  282. 'description' => t('Tokens related to URLs.'),
  283. 'needs-data' => 'path',
  284. );
  285. $info['tokens']['url']['path'] = array(
  286. 'name' => t('Path'),
  287. 'description' => t('The path component of the URL.'),
  288. );
  289. $info['tokens']['url']['relative'] = array(
  290. 'name' => t('Relative URL'),
  291. 'description' => t('The relative URL.'),
  292. );
  293. $info['tokens']['url']['absolute'] = array(
  294. 'name' => t('Absolute URL'),
  295. 'description' => t('The absolute URL.'),
  296. );
  297. $info['tokens']['url']['brief'] = array(
  298. 'name' => t('Brief URL'),
  299. 'description' => t('The URL without the protocol and trailing backslash.'),
  300. );
  301. $info['tokens']['url']['unaliased'] = array(
  302. 'name' => t('Unaliased URL'),
  303. 'description' => t('The unaliased URL.'),
  304. 'type' => 'url',
  305. );
  306. $info['tokens']['url']['args'] = array(
  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'] = 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'] = array(
  319. 'name' => t('First'),
  320. 'description' => t('The first element of the array.'),
  321. );
  322. $info['tokens']['array']['last'] = array(
  323. 'name' => t('Last'),
  324. 'description' => t('The last element of the array.'),
  325. );
  326. $info['tokens']['array']['count'] = array(
  327. 'name' => t('Count'),
  328. 'description' => t('The number of elements in the array.'),
  329. );
  330. $info['tokens']['array']['reversed'] = array(
  331. 'name' => t('Reversed'),
  332. 'description' => t('The array reversed.'),
  333. 'type' => 'array',
  334. );
  335. $info['tokens']['array']['keys'] = array(
  336. 'name' => t('Keys'),
  337. 'description' => t('The array of keys of the array.'),
  338. 'type' => 'array',
  339. );
  340. $info['tokens']['array']['join'] = array(
  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'] = array(
  346. 'name' => t('Value'),
  347. 'description' => t('The specific value of the array.'),
  348. 'dynamic' => TRUE,
  349. );
  350. // Random tokens.
  351. $info['types']['random'] = array(
  352. 'name' => t('Random'),
  353. 'description' => t('Tokens related to random data.'),
  354. );
  355. $info['tokens']['random']['number'] = array(
  356. 'name' => t('Number'),
  357. 'description' => t('A random number from 0 to @max.', array('@max' => mt_getrandmax())),
  358. );
  359. $info['tokens']['random']['hash'] = array(
  360. 'name' => t('Hash'),
  361. 'description' => t('A random hash. The possible hashing algorithms are: @hash-algos.', array('@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(), array $options = array(), BubbleableMetadata $bubbleable_metadata) {
  404. $replacements = array();
  405. $language_manager = \Drupal::languageManager();
  406. $url_options = array('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'] : REQUEST_TIME;
  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, array('date' => REQUEST_TIME), $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, array('url' => $comment->urlInfo('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, array('node' => $source_node), $options, $bubbleable_metadata);
  458. }
  459. if (($node_type_tokens = \Drupal::token()->findWithPrefix($tokens, 'content-type')) && $node_type = node_type_load($node->bundle())) {
  460. $replacements += \Drupal::token()->generate('content-type', $node_type_tokens, array('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, array('url' => $node->urlInfo()), $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->url('edit-form', $url_options);
  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. $replacements[$original] = $root_term->label();
  515. }
  516. break;
  517. }
  518. }
  519. // Chained token relationships.
  520. if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url'))) {
  521. $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $term->urlInfo()), $options, $bubbleable_metadata);
  522. }
  523. // [term:parents:*] chained tokens.
  524. if ($parents_tokens = \Drupal::token()->findWithPrefix($tokens, 'parents')) {
  525. if ($parents = token_taxonomy_term_load_all_parents($term->id(), $langcode)) {
  526. $replacements += \Drupal::token()->generate('array', $parents_tokens, array('array' => $parents), $options, $bubbleable_metadata);
  527. }
  528. }
  529. if ($root_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) {
  530. $parents = $term_storage->loadAllParents($term->id());
  531. $root_term = end($parents);
  532. if ($root_term->tid != $term->id()) {
  533. $replacements += \Drupal::token()->generate('term', $root_tokens, array('term' => $root_term), $options, $bubbleable_metadata);
  534. }
  535. }
  536. }
  537. // Vocabulary tokens.
  538. if ($type == 'vocabulary' && !empty($data['vocabulary'])) {
  539. $vocabulary = $data['vocabulary'];
  540. foreach ($tokens as $name => $original) {
  541. switch ($name) {
  542. case 'machine-name':
  543. $replacements[$original] = $vocabulary->id();
  544. break;
  545. case 'edit-url':
  546. $replacements[$original] = Url::fromRoute('entity.taxonomy_vocabulary.edit_form', ['taxonomy_vocabulary' => $vocabulary->id()], $url_options)->toString();
  547. break;
  548. }
  549. }
  550. }
  551. // File tokens.
  552. if ($type == 'file' && !empty($data['file'])) {
  553. $file = $data['file'];
  554. foreach ($tokens as $name => $original) {
  555. switch ($name) {
  556. case 'basename':
  557. $basename = pathinfo($file->uri->value, PATHINFO_BASENAME);
  558. $replacements[$original] = $basename;
  559. break;
  560. case 'extension':
  561. $extension = pathinfo($file->uri->value, PATHINFO_EXTENSION);
  562. $replacements[$original] = $extension;
  563. break;
  564. case 'size-raw':
  565. $replacements[$original] = (int) $file->filesize->value;
  566. break;
  567. }
  568. }
  569. }
  570. // User tokens.
  571. if ($type == 'user' && !empty($data['user'])) {
  572. /* @var \Drupal\user\UserInterface $account */
  573. $account = $data['user'];
  574. foreach ($tokens as $name => $original) {
  575. switch ($name) {
  576. case 'picture':
  577. if ($account instanceof UserInterface && $account->hasField('user_picture')) {
  578. /** @var \Drupal\Core\Render\RendererInterface $renderer */
  579. $renderer = \Drupal::service('renderer');
  580. $output = [
  581. '#theme' => 'user_picture',
  582. '#account' => $account,
  583. ];
  584. $replacements[$original] = $renderer->renderPlain($output);
  585. }
  586. break;
  587. case 'roles':
  588. $roles = $account->getRoles();
  589. $roles_names = array_combine($roles, $roles);
  590. $replacements[$original] = token_render_array($roles_names, $options);
  591. break;
  592. }
  593. }
  594. // Chained token relationships.
  595. if ($account instanceof UserInterface && $account->hasField('user_picture') && ($picture_tokens = \Drupal::token()->findWithPrefix($tokens, 'picture'))) {
  596. $replacements += \Drupal::token()->generate('file', $picture_tokens, array('file' => $account->user_picture->entity), $options, $bubbleable_metadata);
  597. }
  598. if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
  599. $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $account->urlInfo()), $options, $bubbleable_metadata);
  600. }
  601. if ($role_tokens = \Drupal::token()->findWithPrefix($tokens, 'roles')) {
  602. $roles = $account->getRoles();
  603. $roles_names = array_combine($roles, $roles);
  604. $replacements += \Drupal::token()->generate('array', $role_tokens, array('array' => $roles_names), $options, $bubbleable_metadata);
  605. }
  606. }
  607. // Current user tokens.
  608. if ($type == 'current-user') {
  609. foreach ($tokens as $name => $original) {
  610. switch ($name) {
  611. case 'ip-address':
  612. $ip = \Drupal::request()->getClientIp();
  613. $replacements[$original] = $ip;
  614. break;
  615. }
  616. }
  617. }
  618. // Menu link tokens.
  619. if ($type == 'menu-link' && !empty($data['menu-link'])) {
  620. /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
  621. $link = $data['menu-link'];
  622. /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
  623. $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
  624. if ($link instanceof MenuLinkContentInterface) {
  625. $link = $menu_link_manager->createInstance($link->getPluginId());
  626. }
  627. foreach ($tokens as $name => $original) {
  628. switch ($name) {
  629. case 'id':
  630. $replacements[$original] = $link->getPluginId();
  631. break;
  632. case 'title':
  633. $replacements[$original] = token_menu_link_translated_title($link, $langcode);
  634. break;
  635. case 'url':
  636. $replacements[$original] = $link->getUrlObject()->setAbsolute()->toString();
  637. break;
  638. case 'parent':
  639. /** @var \Drupal\Core\Menu\MenuLinkInterface $parent */
  640. if ($link->getParent() && $parent = $menu_link_manager->createInstance($link->getParent())) {
  641. $replacements[$original] = token_menu_link_translated_title($parent, $langcode);
  642. }
  643. break;
  644. case 'parents':
  645. if ($parents = token_menu_link_load_all_parents($link->getPluginId(), $langcode)) {
  646. $replacements[$original] = token_render_array($parents, $options);
  647. }
  648. break;
  649. case 'root';
  650. if ($link->getParent() && $parent_ids = array_keys(token_menu_link_load_all_parents($link->getPluginId(), $langcode))) {
  651. $root = $menu_link_manager->createInstance(array_shift($parent_ids));
  652. $replacements[$original] = token_menu_link_translated_title($root, $langcode);
  653. }
  654. break;
  655. }
  656. }
  657. // Chained token relationships.
  658. /** @var \Drupal\Core\Menu\MenuLinkInterface $parent */
  659. if ($link->getParent() && ($parent_tokens = \Drupal::token()->findWithPrefix($tokens, 'parent')) && $parent = $menu_link_manager->createInstance($link->getParent())) {
  660. $replacements += \Drupal::token()->generate('menu-link', $parent_tokens, array('menu-link' => $parent), $options, $bubbleable_metadata);
  661. }
  662. // [menu-link:parents:*] chained tokens.
  663. if ($parents_tokens = \Drupal::token()->findWithPrefix($tokens, 'parents')) {
  664. if ($parents = token_menu_link_load_all_parents($link->getPluginId(), $langcode)) {
  665. $replacements += \Drupal::token()->generate('array', $parents_tokens, array('array' => $parents), $options, $bubbleable_metadata);
  666. }
  667. }
  668. if (($root_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) && $link->getParent() && $parent_ids = array_keys(token_menu_link_load_all_parents($link->getPluginId(), $langcode))) {
  669. $root = $menu_link_manager->createInstance(array_shift($parent_ids));
  670. $replacements += \Drupal::token()->generate('menu-link', $root_tokens, array('menu-link' => $root), $options, $bubbleable_metadata);
  671. }
  672. if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
  673. $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $link->getUrlObject()), $options, $bubbleable_metadata);
  674. }
  675. }
  676. // Current page tokens.
  677. if ($type == 'current-page') {
  678. foreach ($tokens as $name => $original) {
  679. switch ($name) {
  680. case 'title':
  681. $request = \Drupal::request();
  682. $route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT);
  683. if ($route) {
  684. $title = \Drupal::service('title_resolver')->getTitle($request, $route);
  685. $replacements[$original] = token_render_array_value($title);
  686. }
  687. break;
  688. case 'url':
  689. $replacements[$original] = Url::fromRoute('<current>', [], $url_options)->toString();
  690. break;
  691. case 'page-number':
  692. if ($page = \Drupal::request()->query->get('page')) {
  693. // @see PagerDefault::execute()
  694. $pager_page_array = explode(',', $page);
  695. $page = $pager_page_array[0];
  696. }
  697. $replacements[$original] = (int) $page + 1;
  698. break;
  699. }
  700. }
  701. // @deprecated
  702. // [current-page:arg] dynamic tokens.
  703. if ($arg_tokens = \Drupal::token()->findWithPrefix($tokens, 'arg')) {
  704. $path = ltrim(\Drupal::service('path.current')->getPath(), '/');
  705. // Make sure its a system path.
  706. $path = \Drupal::service('path.alias_manager')->getPathByAlias($path);
  707. foreach ($arg_tokens as $name => $original) {
  708. $parts = explode('/', $path);
  709. if (is_numeric($name) && isset($parts[$name])) {
  710. $replacements[$original] = $parts[$name];
  711. }
  712. }
  713. }
  714. // [current-page:query] dynamic tokens.
  715. if ($query_tokens = \Drupal::token()->findWithPrefix($tokens, 'query')) {
  716. foreach ($query_tokens as $name => $original) {
  717. if (\Drupal::request()->query->has($name)) {
  718. $value = \Drupal::request()->query->get($name);
  719. $replacements[$original] = $value;
  720. }
  721. }
  722. }
  723. // Chained token relationships.
  724. if ($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) {
  725. $url = Url::fromRoute('<current>');
  726. $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $url), $options, $bubbleable_metadata);
  727. }
  728. }
  729. // URL tokens.
  730. if ($type == 'url' && !empty($data['url'])) {
  731. /** @var \Drupal\Core\Url $url */
  732. $url = $data['url'];
  733. // To retrieve the correct path, modify a copy of the Url object.
  734. $path_url = clone $url;
  735. $path = '/';
  736. // Ensure the URL is routed to avoid throwing an exception.
  737. if ($url->isRouted()) {
  738. $path .= $path_url->setAbsolute(FALSE)->setOption('fragment', NULL)->getInternalPath();
  739. }
  740. foreach ($tokens as $name => $original) {
  741. switch ($name) {
  742. case 'path':
  743. $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode) : $path;
  744. $replacements[$original] = $value;
  745. break;
  746. case 'alias':
  747. // @deprecated
  748. $alias = \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode);
  749. $replacements[$original] = $alias;
  750. break;
  751. case 'absolute':
  752. $replacements[$original] = $url->setAbsolute()->toString();
  753. break;
  754. case 'relative':
  755. $replacements[$original] = $url->setAbsolute(FALSE)->toString();
  756. break;
  757. case 'brief':
  758. $replacements[$original] = preg_replace(array('!^https?://!', '!/$!'), '', $url->setAbsolute()->toString());
  759. break;
  760. case 'unaliased':
  761. $unaliased = clone $url;
  762. $replacements[$original] = $unaliased->setAbsolute()->setOption('alias', TRUE)->toString();
  763. break;
  764. case 'args':
  765. $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode) : $path;
  766. $replacements[$original] = token_render_array(explode('/', $value), $options);
  767. break;
  768. }
  769. }
  770. // [url:args:*] chained tokens.
  771. if ($arg_tokens = \Drupal::token()->findWithPrefix($tokens, 'args')) {
  772. $value = !($url->getOption('alias')) ? \Drupal::service('path.alias_manager')->getAliasByPath($path, $langcode) : $path;
  773. $replacements += \Drupal::token()->generate('array', $arg_tokens, array('array' => explode('/', ltrim($value, '/'))), $options, $bubbleable_metadata);
  774. }
  775. // [url:unaliased:*] chained tokens.
  776. if ($unaliased_tokens = \Drupal::token()->findWithPrefix($tokens, 'unaliased')) {
  777. $url->setOption('alias', TRUE);
  778. $replacements += \Drupal::token()->generate('url', $unaliased_tokens, array('url' => $url), $options, $bubbleable_metadata);
  779. }
  780. }
  781. // Entity tokens.
  782. if (!empty($data[$type]) && $entity_type = \Drupal::service('token.entity_mapper')->getEntityTypeForTokenType($type)) {
  783. /* @var \Drupal\Core\Entity\EntityInterface $entity */
  784. $entity = $data[$type];
  785. foreach ($tokens as $name => $original) {
  786. switch ($name) {
  787. case 'url':
  788. if (_token_module($type, 'url') == 'token' && $url = $entity->url()) {
  789. $replacements[$original] = $url;
  790. }
  791. break;
  792. case 'original':
  793. if (_token_module($type, 'original') == 'token' && !empty($entity->original)) {
  794. $label = $entity->original->label();
  795. $replacements[$original] = $label;
  796. }
  797. break;
  798. }
  799. }
  800. // [entity:url:*] chained tokens.
  801. if (($url_tokens = \Drupal::token()->findWithPrefix($tokens, 'url')) && _token_module($type, 'url') == 'token') {
  802. $replacements += \Drupal::token()->generate('url', $url_tokens, array('url' => $entity->toUrl()), $options, $bubbleable_metadata);
  803. }
  804. // [entity:original:*] chained tokens.
  805. if (($original_tokens = \Drupal::token()->findWithPrefix($tokens, 'original')) && _token_module($type, 'original') == 'token' && !empty($entity->original)) {
  806. $replacements += \Drupal::token()->generate($type, $original_tokens, array($type => $entity->original), $options, $bubbleable_metadata);
  807. }
  808. // Pass through to an generic 'entity' token type generation.
  809. $entity_data = array(
  810. 'entity_type' => $entity_type,
  811. 'entity' => $entity,
  812. 'token_type' => $type,
  813. );
  814. // @todo Investigate passing through more data like everything from entity_extract_ids().
  815. $replacements += \Drupal::token()->generate('entity', $tokens, $entity_data, $options, $bubbleable_metadata);
  816. }
  817. // Array tokens.
  818. if ($type == 'array' && !empty($data['array']) && is_array($data['array'])) {
  819. $array = $data['array'];
  820. $sort = isset($options['array sort']) ? $options['array sort'] : TRUE;
  821. $keys = token_element_children($array, $sort);
  822. /** @var \Drupal\Core\Render\RendererInterface $renderer */
  823. $renderer = \Drupal::service('renderer');
  824. foreach ($tokens as $name => $original) {
  825. switch ($name) {
  826. case 'first':
  827. $value = $array[$keys[0]];
  828. $value = is_array($value) ? $renderer->renderPlain($value) : (string) $value;
  829. $replacements[$original] = $value;
  830. break;
  831. case 'last':
  832. $value = $array[$keys[count($keys) - 1]];
  833. $value = is_array($value) ? $renderer->renderPlain($value) : (string) $value;
  834. $replacements[$original] =$value;
  835. break;
  836. case 'count':
  837. $replacements[$original] = count($keys);
  838. break;
  839. case 'keys':
  840. $replacements[$original] = token_render_array($keys, $options);
  841. break;
  842. case 'reversed':
  843. $reversed = array_reverse($array, TRUE);
  844. $replacements[$original] = token_render_array($reversed, $options);
  845. break;
  846. case 'join':
  847. $replacements[$original] = token_render_array($array, array('join' => '') + $options);
  848. break;
  849. }
  850. }
  851. // [array:value:*] dynamic tokens.
  852. if ($value_tokens = \Drupal::token()->findWithPrefix($tokens, 'value')) {
  853. foreach ($value_tokens as $key => $original) {
  854. if ($key[0] !== '#' && isset($array[$key])) {
  855. $replacements[$original] = token_render_array_value($array[$key], $options);
  856. }
  857. }
  858. }
  859. // [array:join:*] dynamic tokens.
  860. if ($join_tokens = \Drupal::token()->findWithPrefix($tokens, 'join')) {
  861. foreach ($join_tokens as $join => $original) {
  862. $replacements[$original] = token_render_array($array, array('join' => $join) + $options);
  863. }
  864. }
  865. // [array:keys:*] chained tokens.
  866. if ($key_tokens = \Drupal::token()->findWithPrefix($tokens, 'keys')) {
  867. $replacements += \Drupal::token()->generate('array', $key_tokens, array('array' => $keys), $options, $bubbleable_metadata);
  868. }
  869. // [array:reversed:*] chained tokens.
  870. if ($reversed_tokens = \Drupal::token()->findWithPrefix($tokens, 'reversed')) {
  871. $replacements += \Drupal::token()->generate('array', $reversed_tokens, array('array' => array_reverse($array, TRUE)), array('array sort' => FALSE) + $options, $bubbleable_metadata);
  872. }
  873. // @todo Handle if the array values are not strings and could be chained.
  874. }
  875. // Random tokens.
  876. if ($type == 'random') {
  877. foreach ($tokens as $name => $original) {
  878. switch ($name) {
  879. case 'number':
  880. $replacements[$original] = mt_rand();
  881. break;
  882. }
  883. }
  884. // [custom:hash:*] dynamic token.
  885. if ($hash_tokens = \Drupal::token()->findWithPrefix($tokens, 'hash')) {
  886. $algos = hash_algos();
  887. foreach ($hash_tokens as $name => $original) {
  888. if (in_array($name, $algos)) {
  889. $replacements[$original] = hash($name, Crypt::randomBytes(55));
  890. }
  891. }
  892. }
  893. }
  894. // If $type is a token type, $data[$type] is empty but $data[$entity_type] is
  895. // not, re-run token replacements.
  896. if (empty($data[$type]) && ($entity_type = \Drupal::service('token.entity_mapper')->getEntityTypeForTokenType($type)) && $entity_type != $type && !empty($data[$entity_type]) && empty($options['recursive'])) {
  897. $data[$type] = $data[$entity_type];
  898. $options['recursive'] = TRUE;
  899. $replacements += \Drupal::moduleHandler()->invokeAll('tokens', array($type, $tokens, $data, $options, $bubbleable_metadata));
  900. }
  901. // If the token type specifics a 'needs-data' value, and the value is not
  902. // present in $data, then throw an error.
  903. if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
  904. // Only check when tests are running.
  905. $type_info = \Drupal::token()->getTypeInfo($type);
  906. if (!empty($type_info['needs-data']) && !isset($data[$type_info['needs-data']])) {
  907. trigger_error(t('Attempting to perform token replacement for token type %type without required data', array('%type' => $type)), E_USER_WARNING);
  908. }
  909. }
  910. return $replacements;
  911. }
  912. /**
  913. * Implements hook_token_info() on behalf of book.module.
  914. */
  915. function book_token_info() {
  916. $info['types']['book'] = array(
  917. 'name' => t('Book'),
  918. 'description' => t('Tokens related to books.'),
  919. 'needs-data' => 'book',
  920. );
  921. $info['tokens']['book']['title'] = array(
  922. 'name' => t('Title'),
  923. 'description' => t('Title of the book.'),
  924. );
  925. $info['tokens']['book']['author'] = array(
  926. 'name' => t('Author'),
  927. 'description' => t('The author of the book.'),
  928. 'type' => 'user',
  929. );
  930. $info['tokens']['book']['root'] = array(
  931. 'name' => t('Root'),
  932. 'description' => t('Top level of the book.'),
  933. 'type' => 'node',
  934. );
  935. $info['tokens']['book']['parent'] = array(
  936. 'name' => t('Parent'),
  937. 'description' => t('Parent of the current page.'),
  938. 'type' => 'node',
  939. );
  940. $info['tokens']['book']['parents'] = array(
  941. 'name' => t('Parents'),
  942. 'description' => t("An array of all the node's parents, starting with the root."),
  943. 'type' => 'array',
  944. );
  945. $info['tokens']['node']['book'] = array(
  946. 'name' => t('Book'),
  947. 'description' => t('The book page associated with the node.'),
  948. 'type' => 'book',
  949. );
  950. return $info;
  951. }
  952. /**
  953. * Implements hook_tokens() on behalf of book.module.
  954. */
  955. function book_tokens($type, $tokens, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata) {
  956. $replacements = array();
  957. // Node tokens.
  958. if ($type == 'node' && !empty($data['node'])) {
  959. $book = $data['node']->book;
  960. if (!empty($book['bid'])) {
  961. if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'book')) {
  962. $child_node = Node::load($book['nid']);
  963. $replacements += \Drupal::token()->generate('book', $book_tokens, array('book' => $child_node), $options, $bubbleable_metadata);
  964. }
  965. }
  966. }
  967. // Book tokens.
  968. else if ($type == 'book' && !empty($data['book'])) {
  969. $book = $data['book']->book;
  970. if (!empty($book['bid'])) {
  971. $book_node = Node::load($book['bid']);
  972. foreach ($tokens as $name => $original) {
  973. switch ($name) {
  974. case 'root':
  975. case 'title':
  976. $replacements[$original] = $book_node->getTitle();
  977. break;
  978. case 'parent':
  979. if (!empty($book['pid'])) {
  980. $parent_node = Node::load($book['pid']);
  981. $replacements[$original] = $parent_node->getTitle();
  982. }
  983. break;
  984. case 'parents':
  985. if ($parents = token_book_load_all_parents($book)) {
  986. $replacements[$original] = token_render_array($parents, $options);
  987. }
  988. break;
  989. }
  990. }
  991. if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'author')) {
  992. $replacements += \Drupal::token()->generate('user', $book_tokens, array('user' => $book_node->getOwner()), $options, $bubbleable_metadata);
  993. }
  994. if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'root')) {
  995. $replacements += \Drupal::token()->generate('node', $book_tokens, array('node' => $book_node), $options, $bubbleable_metadata);
  996. }
  997. if (!empty($book['pid']) && $book_tokens = \Drupal::token()->findWithPrefix($tokens, 'parent')) {
  998. $parent_node = Node::load($book['pid']);
  999. $replacements += \Drupal::token()->generate('node', $book_tokens, array('node' => $parent_node), $options, $bubbleable_metadata);
  1000. }
  1001. if ($book_tokens = \Drupal::token()->findWithPrefix($tokens, 'parents')) {
  1002. $parents = token_book_load_all_parents($book);
  1003. $replacements += \Drupal::token()->generate('array', $book_tokens, array('array' => $parents), $options, $bubbleable_metadata);
  1004. }
  1005. }
  1006. }
  1007. return $replacements;
  1008. }
  1009. /**
  1010. * Implements hook_token_info() on behalf of menu_ui.module.
  1011. */
  1012. function menu_ui_token_info() {
  1013. // Menu tokens.
  1014. $info['types']['menu'] = array(
  1015. 'name' => t('Menus'),
  1016. 'description' => t('Tokens related to menus.'),
  1017. 'needs-data' => 'menu',
  1018. );
  1019. $info['tokens']['menu']['name'] = array(
  1020. 'name' => t('Name'),
  1021. 'description' => t("The name of the menu."),
  1022. );
  1023. $info['tokens']['menu']['machine-name'] = array(
  1024. 'name' => t('Machine-readable name'),
  1025. 'description' => t("The unique machine-readable name of the menu."),
  1026. );
  1027. $info['tokens']['menu']['description'] = array(
  1028. 'name' => t('Description'),
  1029. 'description' => t('The optional description of the menu.'),
  1030. );
  1031. $info['tokens']['menu']['menu-link-count'] = array(
  1032. 'name' => t('Menu link count'),
  1033. 'description' => t('The number of menu links belonging to the menu.'),
  1034. );
  1035. $info['tokens']['menu']['edit-url'] = array(
  1036. 'name' => t('Edit URL'),
  1037. 'description' => t("The URL of the menu's edit page."),
  1038. );
  1039. $info['tokens']['menu-link']['menu'] = array(
  1040. 'name' => t('Menu'),
  1041. 'description' => t('The menu of the menu link.'),
  1042. 'type' => 'menu',
  1043. );
  1044. $info['tokens']['menu-link']['edit-url'] = array(
  1045. 'name' => t('Edit URL'),
  1046. 'description' => t("The URL of the menu link's edit page."),
  1047. );
  1048. $info['tokens']['node']['menu-link'] = array(
  1049. 'name' => t('Menu link'),
  1050. 'description' => t("The menu link for this node."),
  1051. 'type' => 'menu-link',
  1052. );
  1053. return $info;
  1054. }
  1055. /**
  1056. * Implements hook_tokens() on behalf of menu_ui.module.
  1057. */
  1058. function menu_ui_tokens($type, $tokens, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata) {
  1059. $replacements = array();
  1060. /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
  1061. $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
  1062. $url_options = array('absolute' => TRUE);
  1063. if (isset($options['langcode'])) {
  1064. $url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
  1065. $langcode = $options['langcode'];
  1066. }
  1067. else {
  1068. $langcode = NULL;
  1069. }
  1070. // Node tokens.
  1071. if ($type == 'node' && !empty($data['node'])) {
  1072. /** @var \Drupal\node\NodeInterface $node */
  1073. $node = $data['node'];
  1074. foreach ($tokens as $name => $original) {
  1075. switch ($name) {
  1076. case 'menu-link':
  1077. // On node-form save we populate a calculated field with a menu_link
  1078. // references.
  1079. // @see token_node_menu_link_submit()
  1080. if ($node->getFieldDefinition('menu_link') && $menu_link = $node->menu_link->entity) {
  1081. /** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link */
  1082. $replacements[$original] = $menu_link->getTitle();
  1083. }
  1084. else {
  1085. $url = $node->toUrl();
  1086. if ($links = $menu_link_manager->loadLinksByRoute($url->getRouteName(), $url->getRouteParameters())) {
  1087. $link = _token_menu_link_best_match($node, $links);
  1088. $replacements[$original] = token_menu_link_translated_title($link, $langcode);
  1089. }
  1090. }
  1091. break;
  1092. }
  1093. // Chained token relationships.
  1094. if ($menu_tokens = \Drupal::token()->findWithPrefix($tokens, 'menu-link')) {
  1095. if ($node->getFieldDefinition('menu_link') && $menu_link = $node->menu_link->entity) {
  1096. /** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link */
  1097. $replacements += \Drupal::token()->generate('menu-link', $menu_tokens, array('menu-link' => $menu_link), $options, $bubbleable_metadata);
  1098. }
  1099. else {
  1100. $url = $node->urlInfo();
  1101. if ($links = $menu_link_manager->loadLinksByRoute($url->getRouteName(), $url->getRouteParameters())) {
  1102. $link = _token_menu_link_best_match($node, $links);
  1103. $replacements += \Drupal::token()->generate('menu-link', $menu_tokens, array('menu-link' => $link), $options, $bubbleable_metadata);
  1104. }
  1105. }
  1106. }
  1107. }
  1108. }
  1109. // Menu link tokens.
  1110. if ($type == 'menu-link' && !empty($data['menu-link'])) {
  1111. /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
  1112. $link = $data['menu-link'];
  1113. if ($link instanceof MenuLinkContentInterface) {
  1114. $link = $menu_link_manager->createInstance($link->getPluginId());
  1115. }
  1116. foreach ($tokens as $name => $original) {
  1117. switch ($name) {
  1118. case 'menu':
  1119. if ($menu = Menu::load($link->getMenuName())) {
  1120. $replacements[$original] = $menu->label();
  1121. }
  1122. break;
  1123. case 'edit-url':
  1124. $replacements[$original] = $link->getEditRoute()->setOptions($url_options)->toString();
  1125. break;
  1126. }
  1127. }
  1128. // Chained token relationships.
  1129. if (($menu_tokens = \Drupal::token()->findWithPrefix($tokens, 'menu')) && $menu = Menu::load($link->getMenuName())) {
  1130. $replacements += \Drupal::token()->generate('menu', $menu_tokens, array('menu' => $menu), $options, $bubbleable_metadata);
  1131. }
  1132. }
  1133. // Menu tokens.
  1134. if ($type == 'menu' && !empty($data['menu'])) {
  1135. /** @var \Drupal\system\MenuInterface $menu */
  1136. $menu = $data['menu'];
  1137. foreach ($tokens as $name => $original) {
  1138. switch ($name) {
  1139. case 'name':
  1140. $replacements[$original] = $menu->label();
  1141. break;
  1142. case 'machine-name':
  1143. $replacements[$original] = $menu->id();
  1144. break;
  1145. case 'description':
  1146. $replacements[$original] = $menu->getDescription();
  1147. break;
  1148. case 'menu-link-count':
  1149. $replacements[$original] = $menu_link_manager->countMenuLinks($menu->id());
  1150. break;
  1151. case 'edit-url':
  1152. $replacements[$original] = Url::fromRoute('entity.menu.edit_form', ['menu' => $menu->id()], $url_options)->toString();
  1153. break;
  1154. }
  1155. }
  1156. }
  1157. return $replacements;
  1158. }
  1159. /**
  1160. * Returns a best matched link for a given node.
  1161. *
  1162. * If the url exists in multiple menus, default to the one set on the node
  1163. * itself.
  1164. *
  1165. * @param \Drupal\node\NodeInterface $node
  1166. * The node to look up the default menu settings from.
  1167. * @param array $links
  1168. * An array of instances keyed by plugin ID.
  1169. *
  1170. * @return \Drupal\Core\Menu\MenuLinkInterface
  1171. * A Link instance.
  1172. */
  1173. function _token_menu_link_best_match(NodeInterface $node, array $links) {
  1174. // Get the menu ui defaults so we can determine what menu was
  1175. // selected for this node. This ensures that if the node was added
  1176. // to the menu via the node UI, we use that as a default. If it
  1177. // was not added via the node UI then grab the first in the
  1178. // retrieved array.
  1179. $defaults = menu_ui_get_menu_link_defaults($node);
  1180. if (isset($defaults['id']) && isset($links[$defaults['id']])) {
  1181. $link = $links[$defaults['id']];
  1182. }
  1183. else {
  1184. $link = reset($links);
  1185. }
  1186. return $link;
  1187. }
  1188. /**
  1189. * Implements hook_token_info_alter() on behalf of field.module.
  1190. *
  1191. * We use hook_token_info_alter() rather than hook_token_info() as other
  1192. * modules may already have defined some field tokens.
  1193. */
  1194. function field_token_info_alter(&$info) {
  1195. $type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinitions();
  1196. // Attach field tokens to their respecitve entity tokens.
  1197. foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
  1198. if (!$entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) {
  1199. continue;
  1200. }
  1201. // Make sure a token type exists for this entity.
  1202. $token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity_type_id);
  1203. if (empty($token_type) || !isset($info['types'][$token_type])) {
  1204. continue;
  1205. }
  1206. $fields = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type_id);
  1207. foreach ($fields as $field_name => $field) {
  1208. /** @var \Drupal\field\FieldStorageConfigInterface $field */
  1209. // Ensure the token implements FieldStorageConfigInterface or is defined
  1210. // in token module.
  1211. $provider = '';
  1212. if (isset($info['types'][$token_type]['module'])) {
  1213. $provider = $info['types'][$token_type]['module'];
  1214. }
  1215. if (!($field instanceof FieldStorageConfigInterface) && $provider != 'token') {
  1216. continue;
  1217. }
  1218. // If a token already exists for this field, then don't add it.
  1219. if (isset($info['tokens'][$token_type][$field_name])) {
  1220. continue;
  1221. }
  1222. if ($token_type == 'comment' && $field_name == 'comment_body') {
  1223. // Core provides the comment field as [comment:body].
  1224. continue;
  1225. }
  1226. // Do not define the token type if the field has no properties.
  1227. if (!$field->getPropertyDefinitions()) {
  1228. continue;
  1229. }
  1230. // Generate a description for the token.
  1231. $labels = _token_field_label($entity_type_id, $field_name);
  1232. $label = array_shift($labels);
  1233. $params['@type'] = $type_info[$field->getType()]['label'];
  1234. if (!empty($labels)) {
  1235. $params['%labels'] = implode(', ', $labels);
  1236. $description = t('@type field. Also known as %labels.', $params);
  1237. }
  1238. else {
  1239. $description = t('@type field.', $params);
  1240. }
  1241. $cardinality = $field->getCardinality();
  1242. $cardinality = ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || $cardinality > 3) ? 3 : $cardinality;
  1243. $field_token_name = $token_type . '-' . $field_name;
  1244. $info['tokens'][$token_type][$field_name] = array(
  1245. 'name' => Html::escape($label),
  1246. 'description' => $description,
  1247. 'module' => 'token',
  1248. // For multivalue fields the field token is a list type.
  1249. 'type' => $cardinality > 1 ? "list<$field_token_name>" : $field_token_name,
  1250. );
  1251. // Field token type.
  1252. $info['types'][$field_token_name] = [
  1253. 'name' => Html::escape($label),
  1254. 'description' => t('@label tokens.', ['@label' => Html::escape($label)]),
  1255. 'needs-data' => $field_token_name,
  1256. 'nested' => TRUE,
  1257. ];
  1258. // Field list token type.
  1259. if ($cardinality > 1) {
  1260. $info['types']["list<$field_token_name>"] = array(
  1261. 'name' => t('List of @type values', array('@type' => Html::escape($label))),
  1262. 'description' => t('Tokens for lists of @type values.', array('@type' => Html::escape($label))),
  1263. 'needs-data' => "list<$field_token_name>",
  1264. 'nested' => TRUE,
  1265. );
  1266. }
  1267. // Show a different token for each field delta.
  1268. if ($cardinality > 1) {
  1269. for ($delta = 0; $delta < $cardinality; $delta++) {
  1270. $info['tokens']["list<$field_token_name>"][$delta] = [
  1271. 'name' => t('@type type with delta @delta', ['@type' => Html::escape($label), '@delta' => $delta]),
  1272. 'module' => 'token',
  1273. 'type' => $field_token_name,
  1274. ];
  1275. }
  1276. }
  1277. // Property tokens.
  1278. foreach ($field->getPropertyDefinitions() as $property => $property_definition) {
  1279. if (is_subclass_of($property_definition->getClass(), 'Drupal\Core\TypedData\PrimitiveInterface')) {
  1280. $info['tokens'][$field_token_name][$property] = [
  1281. 'name' => $property_definition->getLabel(),
  1282. 'description' => $property_definition->getDescription(),
  1283. 'module' => 'token',
  1284. ];
  1285. }
  1286. elseif (($property_definition instanceof DataReferenceDefinitionInterface) && ($property_definition->getTargetDefinition() instanceof EntityDataDefinitionInterface)) {
  1287. $referenced_entity_type = $property_definition->getTargetDefinition()->getEntityTypeId();
  1288. $referenced_token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($referenced_entity_type);
  1289. $info['tokens'][$field_token_name][$property] = [
  1290. 'name' => $property_definition->getLabel(),
  1291. 'description' => $property_definition->getDescription(),
  1292. 'module' => 'token',
  1293. 'type' => $referenced_token_type,
  1294. ];
  1295. }
  1296. }
  1297. // Provide image_with_image_style tokens for image fields.
  1298. if ($field->getType() == 'image') {
  1299. $image_styles = image_style_options(FALSE);
  1300. foreach ($image_styles as $style => $description) {
  1301. $info['tokens'][$field_token_name][$style] = [
  1302. 'name' => $description,
  1303. 'description' => t('Represents the image in the given image style.'),
  1304. 'type' => 'image_with_image_style',
  1305. ];
  1306. }
  1307. }
  1308. // Provide format token for datetime fields.
  1309. if ($field->getType() == 'datetime') {
  1310. $info['tokens'][$field_token_name]['date'] = $info['tokens'][$field_token_name]['value'];
  1311. $info['tokens'][$field_token_name]['date']['name'] .= ' ' . t('format');
  1312. $info['tokens'][$field_token_name]['date']['type'] = 'date';
  1313. }
  1314. if ($field->getType() == 'daterange') {
  1315. $info['tokens'][$field_token_name]['start_date'] = $info['tokens'][$field_token_name]['value'];
  1316. $info['tokens'][$field_token_name]['start_date']['name'] .= ' ' . t('format');
  1317. $info['tokens'][$field_token_name]['start_date']['type'] = 'date';
  1318. $info['tokens'][$field_token_name]['end_date'] = $info['tokens'][$field_token_name]['end_value'];
  1319. $info['tokens'][$field_token_name]['end_date']['name'] .= ' ' . t('format');
  1320. $info['tokens'][$field_token_name]['end_date']['type'] = 'date';
  1321. }
  1322. }
  1323. }
  1324. }
  1325. /**
  1326. * Returns the label of a certain field.
  1327. *
  1328. * Therefore it looks up in all bundles to find the most used instance.
  1329. *
  1330. * Based on views_entity_field_label().
  1331. *
  1332. * @todo Resync this method with views_entity_field_label().
  1333. */
  1334. function _token_field_label($entity_type, $field_name) {
  1335. $labels = [];
  1336. // Count the amount of instances per label per field.
  1337. foreach (array_keys(\Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type)) as $bundle) {
  1338. $bundle_instances = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type, $bundle);
  1339. if (isset($bundle_instances[$field_name])) {
  1340. $instance = $bundle_instances[$field_name];
  1341. $label = (string) $instance->getLabel();
  1342. $labels[$label] = isset($labels[$label]) ? ++$labels[$label] : 1;
  1343. }
  1344. }
  1345. if (empty($labels)) {
  1346. return [$field_name];
  1347. }
  1348. // Sort the field labels by it most used label and return the labels.
  1349. arsort($labels);
  1350. return array_keys($labels);
  1351. }
  1352. /**
  1353. * Implements hook_tokens() on behalf of field.module.
  1354. */
  1355. function field_tokens($type, $tokens, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata) {
  1356. $replacements = array();
  1357. $langcode = isset($options['langcode']) ? $options['langcode'] : NULL;
  1358. // Entity tokens.
  1359. if ($type == 'entity' && !empty($data['entity_type']) && !empty($data['entity']) && !empty($data['token_type'])) {
  1360. /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */
  1361. $entity = $data['entity'];
  1362. if (!($entity instanceof ContentEntityInterface)) {
  1363. return $replacements;
  1364. }
  1365. if (!isset($options['langcode'])) {
  1366. // Set the active language in $options, so that it is passed along.
  1367. $langcode = $options['langcode'] = $entity->language()->getId();
  1368. }
  1369. // Obtain the entity with the correct language.
  1370. $entity = \Drupal::service('entity.repository')->getTranslationFromContext($entity, $langcode);
  1371. $view_mode_name = $entity->getEntityTypeId() . '.' . $entity->bundle() . '.token';
  1372. $view_display = \Drupal::entityTypeManager()->getStorage('entity_view_display')->load($view_mode_name);
  1373. $token_view_display = (!empty($view_display) && $view_display->status());
  1374. foreach ($tokens as $name => $original) {
  1375. // For the [entity:field_name] token.
  1376. if (strpos($name, ':') === FALSE) {
  1377. $field_name = $name;
  1378. $token_name = $name;
  1379. }
  1380. // For [entity:field_name:0], [entity:field_name:0:value] and
  1381. // [entity:field_name:value] tokens.
  1382. else {
  1383. list($field_name, $delta) = explode(':', $name, 2);
  1384. if (!is_numeric($delta)) {
  1385. unset($delta);
  1386. }
  1387. $token_name = $field_name;
  1388. }
  1389. // Ensure the entity has the requested field and that the token for it is
  1390. // defined by token.module.
  1391. if (!$entity->hasField($field_name) || _token_module($data['token_type'], $token_name) != 'token') {
  1392. continue;
  1393. }
  1394. $display_options = 'token';
  1395. // Do not continue if the field is empty.
  1396. if ($entity->get($field_name)->isEmpty()) {
  1397. continue;
  1398. }
  1399. // Handle [entity:field_name] and [entity:field_name:0] tokens.
  1400. if ($field_name === $name || isset($delta)) {
  1401. if (!$token_view_display) {
  1402. // We don't have the token view display and should fall back on
  1403. // default formatters. If the field has specified a specific formatter
  1404. // to be used by default with tokens, use that, otherwise use the
  1405. // default formatter.
  1406. /** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
  1407. $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
  1408. $field_type_definition = $field_type_manager->getDefinition($entity->getFieldDefinition($field_name)->getType());
  1409. $display_options = [
  1410. 'type' => !empty($field_type_definition['default_token_formatter']) ? $field_type_definition['default_token_formatter'] : $field_type_definition['default_formatter'],
  1411. 'label' => 'hidden',
  1412. ];
  1413. }
  1414. // Render only one delta.
  1415. if (isset($delta)) {
  1416. if ($field_delta = $entity->{$field_name}[$delta]) {
  1417. $field_output = $field_delta->view($display_options);
  1418. }
  1419. // If no such delta exists, let's not replace the token.
  1420. else {
  1421. continue;
  1422. }
  1423. }
  1424. // Render the whole field (with all deltas).
  1425. else {
  1426. $field_output = $entity->$field_name->view($display_options);
  1427. // If we are displaying all field items we need this #pre_render
  1428. // callback.
  1429. $field_output['#pre_render'][] = 'token_pre_render_field_token';
  1430. }
  1431. $field_output['#token_options'] = $options;
  1432. $replacements[$original] = \Drupal::service('renderer')->renderPlain($field_output);
  1433. }
  1434. // Handle [entity:field_name:value] and [entity:field_name:0:value]
  1435. // tokens.
  1436. else if ($field_tokens = \Drupal::token()->findWithPrefix($tokens, $field_name)) {
  1437. $property_token_data = [
  1438. 'field_property' => TRUE,
  1439. $data['entity_type'] . '-' . $field_name => $entity->$field_name,
  1440. 'field_name' => $data['entity_type'] . '-' . $field_name,
  1441. ];
  1442. $replacements += \Drupal::token()->generate($field_name, $field_tokens, $property_token_data, $options, $bubbleable_metadata);
  1443. }
  1444. }
  1445. // Remove the cloned object from memory.
  1446. unset($entity);
  1447. }
  1448. elseif (!empty($data['field_property'])) {
  1449. foreach ($tokens as $token => $original) {
  1450. $filtered_tokens = $tokens;
  1451. $delta = 0;
  1452. $parts = explode(':', $token);
  1453. if (is_numeric($parts[0])) {
  1454. if (count($parts) > 1) {
  1455. $delta = $parts[0];
  1456. $property_name = $parts[1];
  1457. // Pre-filter the tokens to select those with the correct delta.
  1458. $filtered_tokens = \Drupal::token()->findWithPrefix($tokens, $delta);
  1459. // Remove the delta to unify between having and not having one.
  1460. array_shift($parts);
  1461. }
  1462. else {
  1463. // Token is fieldname:delta, which is invalid.
  1464. continue;
  1465. }
  1466. }
  1467. else {
  1468. $property_name = $parts[0];
  1469. }
  1470. if (isset($data[$data['field_name']][$delta])) {
  1471. $field_item = $data[$data['field_name']][$delta];
  1472. }
  1473. else {
  1474. // The field has no such delta, abort replacement.
  1475. continue;
  1476. }
  1477. if (isset($field_item->$property_name) && ($field_item->$property_name instanceof FieldableEntityInterface)) {
  1478. // Entity reference field.
  1479. $entity = $field_item->$property_name;
  1480. // Obtain the referenced entity with the correct language.
  1481. $entity = \Drupal::service('entity.repository')->getTranslationFromContext($entity, $langcode);
  1482. if (count($parts) > 1) {
  1483. $field_tokens = \Drupal::token()->findWithPrefix($filtered_tokens, $property_name);
  1484. $token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity->getEntityTypeId(), TRUE);
  1485. $replacements += \Drupal::token()->generate($token_type, $field_tokens, [$token_type => $entity], $options, $bubbleable_metadata);
  1486. }
  1487. else {
  1488. $replacements[$original] = $entity->label();
  1489. }
  1490. }
  1491. elseif (($field_item->getFieldDefinition()->getType() == 'image') && ($style = ImageStyle::load($property_name))) {
  1492. // Handle [node:field_name:image_style:property] tokens and multivalued
  1493. // [node:field_name:delta:image_style:property] tokens. If the token is
  1494. // of the form [node:field_name:image_style], provide the URL as a
  1495. // replacement.
  1496. $property_name = isset($parts[1]) ? $parts[1] : 'url';
  1497. $entity = $field_item->entity;
  1498. if (!empty($field_item->entity)) {
  1499. $original_uri = $entity->getFileUri();
  1500. // Only generate the image derivative if needed.
  1501. if ($property_name === 'width' || $property_name === 'height') {
  1502. $dimensions = [
  1503. 'width' => $field_item->width,
  1504. 'height' => $field_item->height,
  1505. ];
  1506. $style->transformDimensions($dimensions, $original_uri);
  1507. $replacements[$original] = $dimensions[$property_name];
  1508. }
  1509. elseif ($property_name === 'uri') {
  1510. $replacements[$original] = $style->buildUri($original_uri);
  1511. }
  1512. elseif ($property_name === 'url') {
  1513. $replacements[$original] = $style->buildUrl($original_uri);
  1514. }
  1515. else {
  1516. // Generate the image derivative, if it doesn't already exist.
  1517. $derivative_uri = $style->buildUri($original_uri);
  1518. $derivative_exists = TRUE;
  1519. if (!file_exists($derivative_uri)) {
  1520. $derivative_exists = $style->createDerivative($original_uri, $derivative_uri);
  1521. }
  1522. if ($derivative_exists) {
  1523. $image = \Drupal::service('image.factory')->get($derivative_uri);
  1524. // Provide the replacement.
  1525. switch ($property_name) {
  1526. case 'mimetype':
  1527. $replacements[$original] = $image->getMimeType();
  1528. break;
  1529. case 'filesize' :
  1530. $replacements[$original] = $image->getFileSize();
  1531. break;
  1532. }
  1533. }
  1534. }
  1535. }
  1536. }
  1537. elseif (in_array($field_item->getFieldDefinition()->getType(), ['datetime', 'daterange']) && in_array($property_name, ['date', 'start_date', 'end_date'])) {
  1538. $timestamp = $field_item->$property_name->getTimestamp();
  1539. // If the token is an exact match for the property or the delta and the
  1540. // property, use the timestamp as-is.
  1541. if($property_name == $token || "$delta:$property_name" == $token) {
  1542. $replacements[$original] = $timestamp;
  1543. }
  1544. else {
  1545. $date_tokens = \Drupal::token()->findWithPrefix($filtered_tokens, $property_name);
  1546. $replacements += \Drupal::token()->generate('date', $date_tokens, ['date' => $timestamp], $options, $bubbleable_metadata);
  1547. }
  1548. }
  1549. else {
  1550. $replacements[$original] = $field_item->$property_name;
  1551. }
  1552. }
  1553. }
  1554. return $replacements;
  1555. }
  1556. /**
  1557. * Pre-render callback for field output used with tokens.
  1558. */
  1559. function token_pre_render_field_token($elements) {
  1560. // Remove the field theme hook, attachments, and JavaScript states.
  1561. unset($elements['#theme']);
  1562. unset($elements['#states']);
  1563. unset($elements['#attached']);
  1564. // Prevent multi-value fields from appearing smooshed together by appending
  1565. // a join suffix to all but the last value.
  1566. $deltas = Element::getVisibleChildren($elements);
  1567. $count = count($deltas);
  1568. if ($count > 1) {
  1569. $join = isset($elements['#token_options']['join']) ? $elements['#token_options']['join'] : ", ";
  1570. foreach ($deltas as $index => $delta) {
  1571. // Do not add a suffix to the last item.
  1572. if ($index < ($count - 1)) {
  1573. $elements[$delta] += array('#suffix' => $join);
  1574. }
  1575. }
  1576. }
  1577. return $elements;
  1578. }