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