LinkGenerator.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. <?php
  2. namespace Drupal\Core\Utility;
  3. use Drupal\Component\Serialization\Json;
  4. use Drupal\Component\Utility\Html;
  5. use Drupal\Component\Render\MarkupInterface;
  6. use Drupal\Core\Extension\ModuleHandlerInterface;
  7. use Drupal\Core\GeneratedLink;
  8. use Drupal\Core\GeneratedButton;
  9. use Drupal\Core\GeneratedNoLink;
  10. use Drupal\Core\Link;
  11. use Drupal\Core\Render\RendererInterface;
  12. use Drupal\Core\Routing\UrlGeneratorInterface;
  13. use Drupal\Core\Template\Attribute;
  14. use Drupal\Core\Url;
  15. /**
  16. * Provides a class which generates a link with route names and parameters.
  17. */
  18. class LinkGenerator implements LinkGeneratorInterface {
  19. /**
  20. * The url generator.
  21. *
  22. * @var \Drupal\Core\Routing\UrlGeneratorInterface
  23. */
  24. protected $urlGenerator;
  25. /**
  26. * The module handler firing the route_link alter hook.
  27. *
  28. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  29. */
  30. protected $moduleHandler;
  31. /**
  32. * The renderer service.
  33. *
  34. * @var \Drupal\Core\Render\RendererInterface
  35. */
  36. protected $renderer;
  37. /**
  38. * Constructs a LinkGenerator instance.
  39. *
  40. * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
  41. * The url generator.
  42. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  43. * The module handler.
  44. * @param \Drupal\Core\Render\RendererInterface $renderer
  45. * The renderer service.
  46. */
  47. public function __construct(UrlGeneratorInterface $url_generator, ModuleHandlerInterface $module_handler, RendererInterface $renderer) {
  48. $this->urlGenerator = $url_generator;
  49. $this->moduleHandler = $module_handler;
  50. $this->renderer = $renderer;
  51. }
  52. /**
  53. * {@inheritdoc}
  54. */
  55. public function generateFromLink(Link $link) {
  56. return $this->generate($link->getText(), $link->getUrl());
  57. }
  58. /**
  59. * {@inheritdoc}
  60. *
  61. * For anonymous users, the "active" class will be calculated on the server,
  62. * because most sites serve each anonymous user the same cached page anyway.
  63. * For authenticated users, the "active" class will be calculated on the
  64. * client (through JavaScript), only data- attributes are added to links to
  65. * prevent breaking the render cache. The JavaScript is added in
  66. * system_page_attachments().
  67. *
  68. * @see system_page_attachments()
  69. */
  70. public function generate($text, Url $url) {
  71. // The link generator should not modify the original URL object, this
  72. // ensures consistent rendering.
  73. // @see https://www.drupal.org/node/2842399
  74. $url = clone $url;
  75. // Performance: avoid Url::toString() needing to retrieve the URL generator
  76. // service from the container.
  77. $url->setUrlGenerator($this->urlGenerator);
  78. if (is_array($text)) {
  79. $text = $this->renderer->render($text);
  80. }
  81. // Start building a structured representation of our link to be altered later.
  82. $variables = [
  83. 'text' => $text,
  84. 'url' => $url,
  85. 'options' => $url->getOptions(),
  86. ];
  87. // Merge in default options.
  88. $variables['options'] += [
  89. 'attributes' => [],
  90. 'query' => [],
  91. 'language' => NULL,
  92. 'set_active_class' => FALSE,
  93. 'absolute' => FALSE,
  94. ];
  95. // Add a hreflang attribute if we know the language of this link's url and
  96. // hreflang has not already been set.
  97. if (!empty($variables['options']['language']) && !isset($variables['options']['attributes']['hreflang'])) {
  98. $variables['options']['attributes']['hreflang'] = $variables['options']['language']->getId();
  99. }
  100. // Ensure that query values are strings.
  101. array_walk($variables['options']['query'], function (&$value) {
  102. if ($value instanceof MarkupInterface) {
  103. $value = (string) $value;
  104. }
  105. });
  106. // Set the "active" class if the 'set_active_class' option is not empty.
  107. if (!empty($variables['options']['set_active_class']) && !$url->isExternal()) {
  108. // Add a "data-drupal-link-query" attribute to let the
  109. // drupal.active-link library know the query in a standardized manner.
  110. if (!empty($variables['options']['query'])) {
  111. $query = $variables['options']['query'];
  112. ksort($query);
  113. $variables['options']['attributes']['data-drupal-link-query'] = Json::encode($query);
  114. }
  115. // Add a "data-drupal-link-system-path" attribute to let the
  116. // drupal.active-link library know the path in a standardized manner.
  117. if ($url->isRouted() && !isset($variables['options']['attributes']['data-drupal-link-system-path'])) {
  118. // @todo System path is deprecated - use the route name and parameters.
  119. $system_path = $url->getInternalPath();
  120. // Special case for the front page.
  121. if ($url->getRouteName() === '<front>') {
  122. $system_path = '<front>';
  123. }
  124. if (!empty($system_path)) {
  125. $variables['options']['attributes']['data-drupal-link-system-path'] = $system_path;
  126. }
  127. }
  128. }
  129. // Remove all HTML and PHP tags from a tooltip, calling expensive strip_tags()
  130. // only when a quick strpos() gives suspicion tags are present.
  131. if (isset($variables['options']['attributes']['title']) && strpos($variables['options']['attributes']['title'], '<') !== FALSE) {
  132. $variables['options']['attributes']['title'] = strip_tags($variables['options']['attributes']['title']);
  133. }
  134. // Allow other modules to modify the structure of the link.
  135. $this->moduleHandler->alter('link', $variables);
  136. $url = $variables['url'];
  137. // Move attributes out of options since generateFromRoute() doesn't need
  138. // them. Make sure the "href" comes first for testing purposes.
  139. $attributes = ['href' => ''] + $variables['options']['attributes'];
  140. unset($variables['options']['attributes']);
  141. $url->setOptions($variables['options']);
  142. // External URLs can not have cacheable metadata.
  143. if ($url->isExternal()) {
  144. $generated_link = new GeneratedLink();
  145. $attributes['href'] = $url->toString(FALSE);
  146. return $this->doGenerate($generated_link, $attributes, $variables);
  147. }
  148. if ($url->isRouted() && $url->getRouteName() === '<nolink>') {
  149. $generated_link = new GeneratedNoLink();
  150. unset($attributes['href']);
  151. return $this->doGenerate($generated_link, $attributes, $variables);
  152. }
  153. if ($url->isRouted() && $url->getRouteName() === '<button>') {
  154. $generated_link = new GeneratedButton();
  155. $attributes['type'] = 'button';
  156. unset($attributes['href']);
  157. return $this->doGenerate($generated_link, $attributes, $variables);
  158. }
  159. $generated_url = $url->toString(TRUE);
  160. $generated_link = GeneratedLink::createFromObject($generated_url);
  161. // The result of the URL generator is a plain-text URL to use as the href
  162. // attribute, and it is escaped by \Drupal\Core\Template\Attribute.
  163. $attributes['href'] = $generated_url->getGeneratedUrl();
  164. return $this->doGenerate($generated_link, $attributes, $variables);
  165. }
  166. /**
  167. * Generates the link.
  168. *
  169. * @param Drupal\Core\GeneratedLink $generated_link
  170. * The generated link, along with its associated cacheability metadata.
  171. * @param array $attributes
  172. * The attributes of the generated link.
  173. * @param array $variables
  174. * The link text, url, and other options.
  175. *
  176. * @return Drupal\Core\GeneratedLink
  177. * The generated link, along with its associated cacheability metadata.
  178. */
  179. protected function doGenerate($generated_link, $attributes, $variables) {
  180. if (!($variables['text'] instanceof MarkupInterface)) {
  181. $variables['text'] = Html::escape($variables['text']);
  182. }
  183. $attributes = new Attribute($attributes);
  184. // This is safe because Attribute does escaping and $variables['text'] is
  185. // either rendered or escaped.
  186. return $generated_link->setGeneratedLink('<' . $generated_link::TAG . $attributes . '>' . $variables['text'] . '</' . $generated_link::TAG . '>');
  187. }
  188. }