Upgraded drupal core with security updates
[yaffs-website] / web / core / lib / Drupal / Core / Url.php
1 <?php
2
3 namespace Drupal\Core;
4
5 use Drupal\Component\Utility\NestedArray;
6 use Drupal\Component\Utility\UrlHelper;
7 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
8 use Drupal\Core\Routing\RouteMatchInterface;
9 use Drupal\Core\Routing\UrlGeneratorInterface;
10 use Drupal\Core\Session\AccountInterface;
11 use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
12 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
13 use Symfony\Component\HttpFoundation\Request;
14
15 /**
16  * Defines an object that holds information about a URL.
17  */
18 class Url {
19   use DependencySerializationTrait;
20
21   /**
22    * The URL generator.
23    *
24    * @var \Drupal\Core\Routing\UrlGeneratorInterface
25    */
26   protected $urlGenerator;
27
28   /**
29    * The unrouted URL assembler.
30    *
31    * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface
32    */
33   protected $urlAssembler;
34
35   /**
36    * The access manager
37    *
38    * @var \Drupal\Core\Access\AccessManagerInterface
39    */
40   protected $accessManager;
41
42   /**
43    * The route name.
44    *
45    * @var string
46    */
47   protected $routeName;
48
49   /**
50    * The route parameters.
51    *
52    * @var array
53    */
54   protected $routeParameters = [];
55
56   /**
57    * The URL options.
58    *
59    * See \Drupal\Core\Url::fromUri() for details on the options.
60    *
61    * @var array
62    */
63   protected $options = [];
64
65   /**
66    * Indicates whether this object contains an external URL.
67    *
68    * @var bool
69    */
70   protected $external = FALSE;
71
72   /**
73    * Indicates whether this URL is for a URI without a Drupal route.
74    *
75    * @var bool
76    */
77   protected $unrouted = FALSE;
78
79   /**
80    * The non-route URI.
81    *
82    * Only used if self::$unrouted is TRUE.
83    *
84    * @var string
85    */
86   protected $uri;
87
88   /**
89    * Stores the internal path, if already requested by getInternalPath().
90    *
91    * @var string
92    */
93   protected $internalPath;
94
95   /**
96    * Constructs a new Url object.
97    *
98    * In most cases, use Url::fromRoute() or Url::fromUri() rather than
99    * constructing Url objects directly in order to avoid ambiguity and make your
100    * code more self-documenting.
101    *
102    * @param string $route_name
103    *   The name of the route
104    * @param array $route_parameters
105    *   (optional) An associative array of parameter names and values.
106    * @param array $options
107    *   See \Drupal\Core\Url::fromUri() for details.
108    *
109    * @see static::fromRoute()
110    * @see static::fromUri()
111    *
112    * @todo Update this documentation for non-routed URIs in
113    *   https://www.drupal.org/node/2346787
114    */
115   public function __construct($route_name, $route_parameters = [], $options = []) {
116     $this->routeName = $route_name;
117     $this->routeParameters = $route_parameters;
118     $this->options = $options;
119   }
120
121   /**
122    * Creates a new Url object for a URL that has a Drupal route.
123    *
124    * This method is for URLs that have Drupal routes (that is, most pages
125    * generated by Drupal). For non-routed local URIs relative to the base
126    * path (like robots.txt) use Url::fromUri() with the base: scheme.
127    *
128    * @param string $route_name
129    *   The name of the route
130    * @param array $route_parameters
131    *   (optional) An associative array of route parameter names and values.
132    * @param array $options
133    *   See \Drupal\Core\Url::fromUri() for details.
134    *
135    * @return \Drupal\Core\Url
136    *   A new Url object for a routed (internal to Drupal) URL.
137    *
138    * @see \Drupal\Core\Url::fromUserInput()
139    * @see \Drupal\Core\Url::fromUri()
140    */
141   public static function fromRoute($route_name, $route_parameters = [], $options = []) {
142     return new static($route_name, $route_parameters, $options);
143   }
144
145   /**
146    * Creates a new URL object from a route match.
147    *
148    * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
149    *   The route match.
150    *
151    * @return $this
152    */
153   public static function fromRouteMatch(RouteMatchInterface $route_match) {
154     if ($route_match->getRouteObject()) {
155       return new static($route_match->getRouteName(), $route_match->getRawParameters()->all());
156     }
157     else {
158       throw new \InvalidArgumentException('Route required');
159     }
160   }
161
162   /**
163    * Creates a Url object for a relative URI reference submitted by user input.
164    *
165    * Use this method to create a URL for user-entered paths that may or may not
166    * correspond to a valid Drupal route.
167    *
168    * @param string $user_input
169    *   User input for a link or path. The first character must be one of the
170    *   following characters:
171    *   - '/': A path within the current site. This path might be to a Drupal
172    *     route (e.g., '/admin'), to a file (e.g., '/README.txt'), or to
173    *     something processed by a non-Drupal script (e.g.,
174    *     '/not/a/drupal/page'). If the path matches a Drupal route, then the
175    *     URL generation will include Drupal's path processors (e.g.,
176    *     language-prefixing and aliasing). Otherwise, the URL generation will
177    *     just append the passed-in path to Drupal's base path.
178    *   - '?': A query string for the current page or resource.
179    *   - '#': A fragment (jump-link) on the current page or resource.
180    *   This helps reduce ambiguity for user-entered links and paths, and
181    *   supports user interfaces where users may normally use auto-completion
182    *   to search for existing resources, but also may type one of these
183    *   characters to link to (e.g.) a specific path on the site.
184    *   (With regard to the URI specification, the user input is treated as a
185    *   @link https://tools.ietf.org/html/rfc3986#section-4.2 relative URI reference @endlink
186    *   where the relative part is of type
187    *   @link https://tools.ietf.org/html/rfc3986#section-3.3 path-abempty @endlink.)
188    * @param array $options
189    *   (optional) An array of options. See Url::fromUri() for details.
190    *
191    * @return static
192    *   A new Url object based on user input.
193    *
194    * @throws \InvalidArgumentException
195    *   Thrown when the user input does not begin with one of the following
196    *   characters: '/', '?', or '#'.
197    */
198   public static function fromUserInput($user_input, $options = []) {
199     // Ensuring one of these initial characters also enforces that what is
200     // passed is a relative URI reference rather than an absolute URI,
201     // because these are URI reserved characters that a scheme name may not
202     // start with.
203     if ((strpos($user_input, '/') !== 0) && (strpos($user_input, '#') !== 0) && (strpos($user_input, '?') !== 0)) {
204       throw new \InvalidArgumentException("The user-entered string '$user_input' must begin with a '/', '?', or '#'.");
205     }
206
207     // fromUri() requires an absolute URI, so prepend the appropriate scheme
208     // name.
209     return static::fromUri('internal:' . $user_input, $options);
210   }
211
212   /**
213    * Creates a new Url object from a URI.
214    *
215    * This method is for generating URLs for URIs that:
216    * - do not have Drupal routes: both external URLs and unrouted local URIs
217    *   like base:robots.txt
218    * - do have a Drupal route but have a custom scheme to simplify linking.
219    *   Currently, there is only the entity: scheme (This allows URIs of the
220    *   form entity:{entity_type}/{entity_id}. For example: entity:node/1
221    *   resolves to the entity.node.canonical route with a node parameter of 1.)
222    *
223    * For URLs that have Drupal routes (that is, most pages generated by Drupal),
224    * use Url::fromRoute().
225    *
226    * @param string $uri
227    *   The URI of the resource including the scheme. For user input that may
228    *   correspond to a Drupal route, use internal: for the scheme. For paths
229    *   that are known not to be handled by the Drupal routing system (such as
230    *   static files), use base: for the scheme to get a link relative to the
231    *   Drupal base path (like the <base> HTML element). For a link to an entity
232    *   you may use entity:{entity_type}/{entity_id} URIs. The internal: scheme
233    *   should be avoided except when processing actual user input that may or
234    *   may not correspond to a Drupal route. Normally use Url::fromRoute() for
235    *   code linking to any any Drupal page.
236    * @param array $options
237    *   (optional) An associative array of additional URL options, with the
238    *   following elements:
239    *   - 'query': An array of query key/value-pairs (without any URL-encoding)
240    *     to append to the URL.
241    *   - 'fragment': A fragment identifier (named anchor) to append to the URL.
242    *     Do not include the leading '#' character.
243    *   - 'absolute': Defaults to FALSE. Whether to force the output to be an
244    *     absolute link (beginning with http:). Useful for links that will be
245    *     displayed outside the site, such as in an RSS feed.
246    *   - 'attributes': An associative array of HTML attributes that will be
247    *     added to the anchor tag if you use the \Drupal\Core\Link class to make
248    *     the link.
249    *   - 'language': An optional language object used to look up the alias
250    *     for the URL. If $options['language'] is omitted, it defaults to the
251    *     current language for the language type LanguageInterface::TYPE_URL.
252    *   - 'https': Whether this URL should point to a secure location. If not
253    *     defined, the current scheme is used, so the user stays on HTTP or HTTPS
254    *     respectively. TRUE enforces HTTPS and FALSE enforces HTTP.
255    *
256    * @return \Drupal\Core\Url
257    *   A new Url object with properties depending on the URI scheme. Call the
258    *   access() method on this to do access checking.
259    *
260    * @throws \InvalidArgumentException
261    *   Thrown when the passed in path has no scheme.
262    *
263    * @see \Drupal\Core\Url::fromRoute()
264    * @see \Drupal\Core\Url::fromUserInput()
265    */
266   public static function fromUri($uri, $options = []) {
267     // parse_url() incorrectly parses base:number/... as hostname:port/...
268     // and not the scheme. Prevent that by prefixing the path with a slash.
269     if (preg_match('/^base:\d/', $uri)) {
270       $uri = str_replace('base:', 'base:/', $uri);
271     }
272     $uri_parts = parse_url($uri);
273     if ($uri_parts === FALSE) {
274       throw new \InvalidArgumentException("The URI '$uri' is malformed.");
275     }
276     // We support protocol-relative URLs.
277     if (strpos($uri, '//') === 0) {
278       $uri_parts['scheme'] = '';
279     }
280     elseif (empty($uri_parts['scheme'])) {
281       throw new \InvalidArgumentException("The URI '$uri' is invalid. You must use a valid URI scheme.");
282     }
283     $uri_parts += ['path' => ''];
284     // Discard empty fragment in $options for consistency with parse_url().
285     if (isset($options['fragment']) && strlen($options['fragment']) == 0) {
286       unset($options['fragment']);
287     }
288     // Extract query parameters and fragment and merge them into $uri_options,
289     // but preserve the original $options for the fallback case.
290     $uri_options = $options;
291     if (isset($uri_parts['fragment'])) {
292       $uri_options += ['fragment' => $uri_parts['fragment']];
293       unset($uri_parts['fragment']);
294     }
295
296     if (!empty($uri_parts['query'])) {
297       $uri_query = [];
298       parse_str($uri_parts['query'], $uri_query);
299       $uri_options['query'] = isset($uri_options['query']) ? $uri_options['query'] + $uri_query : $uri_query;
300       unset($uri_parts['query']);
301     }
302
303     if ($uri_parts['scheme'] === 'entity') {
304       $url = static::fromEntityUri($uri_parts, $uri_options, $uri);
305     }
306     elseif ($uri_parts['scheme'] === 'internal') {
307       $url = static::fromInternalUri($uri_parts, $uri_options);
308     }
309     elseif ($uri_parts['scheme'] === 'route') {
310       $url = static::fromRouteUri($uri_parts, $uri_options, $uri);
311     }
312     else {
313       $url = new static($uri, [], $options);
314       if ($uri_parts['scheme'] !== 'base') {
315         $url->external = TRUE;
316         $url->setOption('external', TRUE);
317       }
318       $url->setUnrouted();
319     }
320
321     return $url;
322   }
323
324   /**
325    * Create a new Url object for entity URIs.
326    *
327    * @param array $uri_parts
328    *   Parts from an URI of the form entity:{entity_type}/{entity_id} as from
329    *   parse_url().
330    * @param array $options
331    *   An array of options, see \Drupal\Core\Url::fromUri() for details.
332    * @param string $uri
333    *   The original entered URI.
334    *
335    * @return \Drupal\Core\Url
336    *   A new Url object for an entity's canonical route.
337    *
338    * @throws \InvalidArgumentException
339    *   Thrown if the entity URI is invalid.
340    */
341   protected static function fromEntityUri(array $uri_parts, array $options, $uri) {
342     list($entity_type_id, $entity_id) = explode('/', $uri_parts['path'], 2);
343     if ($uri_parts['scheme'] != 'entity' || $entity_id === '') {
344       throw new \InvalidArgumentException("The entity URI '$uri' is invalid. You must specify the entity id in the URL. e.g., entity:node/1 for loading the canonical path to node entity with id 1.");
345     }
346
347     return new static("entity.$entity_type_id.canonical", [$entity_type_id => $entity_id], $options);
348   }
349
350   /**
351    * Creates a new Url object for 'internal:' URIs.
352    *
353    * Important note: the URI minus the scheme can NOT simply be validated by a
354    * \Drupal\Core\Path\PathValidatorInterface implementation. The semantics of
355    * the 'internal:' URI scheme are different:
356    * - PathValidatorInterface accepts paths without a leading slash (e.g.
357    *   'node/add') as well as 2 special paths: '<front>' and '<none>', which are
358    *   mapped to the correspondingly named routes.
359    * - 'internal:' URIs store paths with a leading slash that represents the
360    *   root â€” i.e. the front page â€” (e.g. 'internal:/node/add'), and doesn't
361    *   have any exceptions.
362    *
363    * To clarify, a few examples of path plus corresponding 'internal:' URI:
364    * - 'node/add' -> 'internal:/node/add'
365    * - 'node/add?foo=bar' -> 'internal:/node/add?foo=bar'
366    * - 'node/add#kitten' -> 'internal:/node/add#kitten'
367    * - '<front>' -> 'internal:/'
368    * - '<front>foo=bar' -> 'internal:/?foo=bar'
369    * - '<front>#kitten' -> 'internal:/#kitten'
370    * - '<none>' -> 'internal:'
371    * - '<none>foo=bar' -> 'internal:?foo=bar'
372    * - '<none>#kitten' -> 'internal:#kitten'
373    *
374    * Therefore, when using a PathValidatorInterface to validate 'internal:'
375    * URIs, we must map:
376    * - 'internal:' (path component is '')  to the special '<none>' path
377    * - 'internal:/' (path component is '/') to the special '<front>' path
378    * - 'internal:/some-path' (path component is '/some-path') to 'some-path'
379    *
380    * @param array $uri_parts
381    *   Parts from an URI of the form internal:{path} as from parse_url().
382    * @param array $options
383    *   An array of options, see \Drupal\Core\Url::fromUri() for details.
384    *
385    * @return \Drupal\Core\Url
386    *   A new Url object for a 'internal:' URI.
387    *
388    * @throws \InvalidArgumentException
389    *   Thrown when the URI's path component doesn't have a leading slash.
390    */
391   protected static function fromInternalUri(array $uri_parts, array $options) {
392     // Both PathValidator::getUrlIfValidWithoutAccessCheck() and 'base:' URIs
393     // only accept/contain paths without a leading slash, unlike 'internal:'
394     // URIs, for which the leading slash means "relative to Drupal root" and
395     // "relative to Symfony app root" (just like in Symfony/Drupal 8 routes).
396     if (empty($uri_parts['path'])) {
397       $uri_parts['path'] = '<none>';
398     }
399     elseif ($uri_parts['path'] === '/') {
400       $uri_parts['path'] = '<front>';
401     }
402     else {
403       if ($uri_parts['path'][0] !== '/') {
404         throw new \InvalidArgumentException("The internal path component '{$uri_parts['path']}' is invalid. Its path component must have a leading slash, e.g. internal:/foo.");
405       }
406       // Remove the leading slash.
407       $uri_parts['path'] = substr($uri_parts['path'], 1);
408
409       if (UrlHelper::isExternal($uri_parts['path'])) {
410         throw new \InvalidArgumentException("The internal path component '{$uri_parts['path']}' is external. You are not allowed to specify an external URL together with internal:/.");
411       }
412     }
413
414     $url = \Drupal::pathValidator()
415       ->getUrlIfValidWithoutAccessCheck($uri_parts['path']) ?: static::fromUri('base:' . $uri_parts['path'], $options);
416     // Allow specifying additional options.
417     $url->setOptions($options + $url->getOptions());
418
419     return $url;
420   }
421
422   /**
423    * Creates a new Url object for 'route:' URIs.
424    *
425    * @param array $uri_parts
426    *   Parts from an URI of the form route:{route_name};{route_parameters} as
427    *   from parse_url(), where the path is the route name optionally followed by
428    *   a ";" followed by route parameters in key=value format with & separators.
429    * @param array $options
430    *   An array of options, see \Drupal\Core\Url::fromUri() for details.
431    * @param string $uri
432    *   The original passed in URI.
433    *
434    * @return \Drupal\Core\Url
435    *   A new Url object for a 'route:' URI.
436    *
437    * @throws \InvalidArgumentException
438    *   Thrown when the route URI does not have a route name.
439    */
440   protected static function fromRouteUri(array $uri_parts, array $options, $uri) {
441     $route_parts = explode(';', $uri_parts['path'], 2);
442     $route_name = $route_parts[0];
443     if ($route_name === '') {
444       throw new \InvalidArgumentException("The route URI '$uri' is invalid. You must have a route name in the URI. e.g., route:system.admin");
445     }
446     $route_parameters = [];
447     if (!empty($route_parts[1])) {
448       parse_str($route_parts[1], $route_parameters);
449     }
450
451     return new static($route_name, $route_parameters, $options);
452   }
453
454   /**
455    * Returns the Url object matching a request.
456    *
457    * SECURITY NOTE: The request path is not checked to be valid and accessible
458    * by the current user to allow storing and reusing Url objects by different
459    * users. The 'path.validator' service getUrlIfValid() method should be used
460    * instead of this one if validation and access check is desired. Otherwise,
461    * 'access_manager' service checkNamedRoute() method should be used on the
462    * router name and parameters stored in the Url object returned by this
463    * method.
464    *
465    * @param \Symfony\Component\HttpFoundation\Request $request
466    *   A request object.
467    *
468    * @return static
469    *   A Url object. Warning: the object is created even if the current user
470    *   would get an access denied running the same request via the normal page
471    *   flow.
472    *
473    * @throws \Drupal\Core\Routing\MatchingRouteNotFoundException
474    *   Thrown when the request cannot be matched.
475    */
476   public static function createFromRequest(Request $request) {
477     // We use the router without access checks because URL objects might be
478     // created and stored for different users.
479     $result = \Drupal::service('router.no_access_checks')->matchRequest($request);
480     $route_name = $result[RouteObjectInterface::ROUTE_NAME];
481     $route_parameters = $result['_raw_variables']->all();
482     return new static($route_name, $route_parameters);
483   }
484
485   /**
486    * Sets this Url to encapsulate an unrouted URI.
487    *
488    * @return $this
489    */
490   protected function setUnrouted() {
491     $this->unrouted = TRUE;
492     // What was passed in as the route name is actually the URI.
493     // @todo Consider fixing this in https://www.drupal.org/node/2346787.
494     $this->uri = $this->routeName;
495     // Set empty route name and parameters.
496     $this->routeName = NULL;
497     $this->routeParameters = [];
498     return $this;
499   }
500
501   /**
502    * Generates a URI string that represents the data in the Url object.
503    *
504    * The URI will typically have the scheme of route: even if the object was
505    * constructed using an entity: or internal: scheme. A internal: URI that
506    * does not match a Drupal route with be returned here with the base: scheme,
507    * and external URLs will be returned in their original form.
508    *
509    * @return string
510    *   A URI representation of the Url object data.
511    */
512   public function toUriString() {
513     if ($this->isRouted()) {
514       $uri = 'route:' . $this->routeName;
515       if ($this->routeParameters) {
516         $uri .= ';' . UrlHelper::buildQuery($this->routeParameters);
517       }
518     }
519     else {
520       $uri = $this->uri;
521     }
522     $query = !empty($this->options['query']) ? ('?' . UrlHelper::buildQuery($this->options['query'])) : '';
523     $fragment = isset($this->options['fragment']) && strlen($this->options['fragment']) ? '#' . $this->options['fragment'] : '';
524     return $uri . $query . $fragment;
525   }
526
527   /**
528    * Indicates if this Url is external.
529    *
530    * @return bool
531    */
532   public function isExternal() {
533     return $this->external;
534   }
535
536   /**
537    * Indicates if this Url has a Drupal route.
538    *
539    * @return bool
540    */
541   public function isRouted() {
542     return !$this->unrouted;
543   }
544
545   /**
546    * Returns the route name.
547    *
548    * @return string
549    *
550    * @throws \UnexpectedValueException.
551    *   If this is a URI with no corresponding route.
552    */
553   public function getRouteName() {
554     if ($this->unrouted) {
555       throw new \UnexpectedValueException('External URLs do not have an internal route name.');
556     }
557
558     return $this->routeName;
559   }
560
561   /**
562    * Returns the route parameters.
563    *
564    * @return array
565    *
566    * @throws \UnexpectedValueException.
567    *   If this is a URI with no corresponding route.
568    */
569   public function getRouteParameters() {
570     if ($this->unrouted) {
571       throw new \UnexpectedValueException('External URLs do not have internal route parameters.');
572     }
573
574     return $this->routeParameters;
575   }
576
577   /**
578    * Sets the route parameters.
579    *
580    * @param array $parameters
581    *   The array of parameters.
582    *
583    * @return $this
584    *
585    * @throws \UnexpectedValueException.
586    *   If this is a URI with no corresponding route.
587    */
588   public function setRouteParameters($parameters) {
589     if ($this->unrouted) {
590       throw new \UnexpectedValueException('External URLs do not have route parameters.');
591     }
592     $this->routeParameters = $parameters;
593     return $this;
594   }
595
596   /**
597    * Sets a specific route parameter.
598    *
599    * @param string $key
600    *   The key of the route parameter.
601    * @param mixed $value
602    *   The route parameter.
603    *
604    * @return $this
605    *
606    * @throws \UnexpectedValueException.
607    *   If this is a URI with no corresponding route.
608    */
609   public function setRouteParameter($key, $value) {
610     if ($this->unrouted) {
611       throw new \UnexpectedValueException('External URLs do not have route parameters.');
612     }
613     $this->routeParameters[$key] = $value;
614     return $this;
615   }
616
617   /**
618    * Returns the URL options.
619    *
620    * @return array
621    *   The array of options. See \Drupal\Core\Url::fromUri() for details on what
622    *   it contains.
623    */
624   public function getOptions() {
625     return $this->options;
626   }
627
628   /**
629    * Gets a specific option.
630    *
631    * See \Drupal\Core\Url::fromUri() for details on the options.
632    *
633    * @param string $name
634    *   The name of the option.
635    *
636    * @return mixed
637    *   The value for a specific option, or NULL if it does not exist.
638    */
639   public function getOption($name) {
640     if (!isset($this->options[$name])) {
641       return NULL;
642     }
643
644     return $this->options[$name];
645   }
646
647   /**
648    * Sets the URL options.
649    *
650    * @param array $options
651    *   The array of options. See \Drupal\Core\Url::fromUri() for details on what
652    *   it contains.
653    *
654    * @return $this
655    */
656   public function setOptions($options) {
657     $this->options = $options;
658     return $this;
659   }
660
661   /**
662    * Sets a specific option.
663    *
664    * See \Drupal\Core\Url::fromUri() for details on the options.
665    *
666    * @param string $name
667    *   The name of the option.
668    * @param mixed $value
669    *   The option value.
670    *
671    * @return $this
672    */
673   public function setOption($name, $value) {
674     $this->options[$name] = $value;
675     return $this;
676   }
677
678   /**
679    * Merges the URL options with any currently set.
680    *
681    * In the case of conflict with existing options, the new options will replace
682    * the existing options.
683    *
684    * @param array $options
685    *   The array of options. See \Drupal\Core\Url::fromUri() for details on what
686    *   it contains.
687    *
688    * @return $this
689    */
690   public function mergeOptions($options) {
691     $this->options = NestedArray::mergeDeep($this->options, $options);
692     return $this;
693   }
694
695   /**
696    * Returns the URI value for this Url object.
697    *
698    * Only to be used if self::$unrouted is TRUE.
699    *
700    * @return string
701    *   A URI not connected to a route. May be an external URL.
702    *
703    * @throws \UnexpectedValueException
704    *   Thrown when the URI was requested for a routed URL.
705    */
706   public function getUri() {
707     if (!$this->unrouted) {
708       throw new \UnexpectedValueException('This URL has a Drupal route, so the canonical form is not a URI.');
709     }
710
711     return $this->uri;
712   }
713
714   /**
715    * Sets the value of the absolute option for this Url.
716    *
717    * @param bool $absolute
718    *   (optional) Whether to make this Url absolute or not. Defaults to TRUE.
719    *
720    * @return $this
721    */
722   public function setAbsolute($absolute = TRUE) {
723     $this->options['absolute'] = $absolute;
724     return $this;
725   }
726
727   /**
728    * Generates the string URL representation for this Url object.
729    *
730    * For an external URL, the string will contain the input plus any query
731    * string or fragment specified by the options array.
732    *
733    * If this Url object was constructed from a Drupal route or from an internal
734    * URI (URIs using the internal:, base:, or entity: schemes), the returned
735    * string will either be a relative URL like /node/1 or an absolute URL like
736    * http://example.com/node/1 depending on the options array, plus any
737    * specified query string or fragment.
738    *
739    * @param bool $collect_bubbleable_metadata
740    *   (optional) Defaults to FALSE. When TRUE, both the generated URL and its
741    *   associated bubbleable metadata are returned.
742    *
743    * @return string|\Drupal\Core\GeneratedUrl
744    *   A string URL.
745    *   When $collect_bubbleable_metadata is TRUE, a GeneratedUrl object is
746    *   returned, containing the generated URL plus bubbleable metadata.
747    */
748   public function toString($collect_bubbleable_metadata = FALSE) {
749     if ($this->unrouted) {
750       return $this->unroutedUrlAssembler()->assemble($this->getUri(), $this->getOptions(), $collect_bubbleable_metadata);
751     }
752
753     return $this->urlGenerator()->generateFromRoute($this->getRouteName(), $this->getRouteParameters(), $this->getOptions(), $collect_bubbleable_metadata);
754   }
755
756   /**
757    * Returns the route information for a render array.
758    *
759    * @return array
760    *   An associative array suitable for a render array.
761    */
762   public function toRenderArray() {
763     $render_array = [
764       '#url' => $this,
765       '#options' => $this->getOptions(),
766     ];
767     if (!$this->unrouted) {
768       $render_array['#access_callback'] = [get_class(), 'renderAccess'];
769     }
770     return $render_array;
771   }
772
773   /**
774    * Returns the internal path (system path) for this route.
775    *
776    * This path will not include any prefixes, fragments, or query strings.
777    *
778    * @return string
779    *   The internal path for this route.
780    *
781    * @throws \UnexpectedValueException.
782    *   If this is a URI with no corresponding system path.
783    */
784   public function getInternalPath() {
785     if ($this->unrouted) {
786       throw new \UnexpectedValueException('Unrouted URIs do not have internal representations.');
787     }
788
789     if (!isset($this->internalPath)) {
790       $this->internalPath = $this->urlGenerator()->getPathFromRoute($this->getRouteName(), $this->getRouteParameters());
791     }
792     return $this->internalPath;
793   }
794
795   /**
796    * Checks this Url object against applicable access check services.
797    *
798    * Determines whether the route is accessible or not.
799    *
800    * @param \Drupal\Core\Session\AccountInterface $account
801    *   (optional) Run access checks for this account. Defaults to the current
802    *   user.
803    *
804    * @return bool
805    *   Returns TRUE if the user has access to the url, otherwise FALSE.
806    */
807   public function access(AccountInterface $account = NULL) {
808     if ($this->isRouted()) {
809       return $this->accessManager()->checkNamedRoute($this->getRouteName(), $this->getRouteParameters(), $account);
810     }
811     return TRUE;
812   }
813
814   /**
815    * Checks a Url render element against applicable access check services.
816    *
817    * @param array $element
818    *   A render element as returned from \Drupal\Core\Url::toRenderArray().
819    *
820    * @return bool
821    *   Returns TRUE if the current user has access to the url, otherwise FALSE.
822    */
823   public static function renderAccess(array $element) {
824     return $element['#url']->access();
825   }
826
827   /**
828    * @return \Drupal\Core\Access\AccessManagerInterface
829    */
830   protected function accessManager() {
831     if (!isset($this->accessManager)) {
832       $this->accessManager = \Drupal::service('access_manager');
833     }
834     return $this->accessManager;
835   }
836
837   /**
838    * Gets the URL generator.
839    *
840    * @return \Drupal\Core\Routing\UrlGeneratorInterface
841    *   The URL generator.
842    */
843   protected function urlGenerator() {
844     if (!$this->urlGenerator) {
845       $this->urlGenerator = \Drupal::urlGenerator();
846     }
847     return $this->urlGenerator;
848   }
849
850   /**
851    * Gets the unrouted URL assembler for non-Drupal URLs.
852    *
853    * @return \Drupal\Core\Utility\UnroutedUrlAssemblerInterface
854    *   The unrouted URL assembler.
855    */
856   protected function unroutedUrlAssembler() {
857     if (!$this->urlAssembler) {
858       $this->urlAssembler = \Drupal::service('unrouted_url_assembler');
859     }
860     return $this->urlAssembler;
861   }
862
863   /**
864    * Sets the URL generator.
865    *
866    * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
867    *   (optional) The URL generator, specify NULL to reset it.
868    *
869    * @return $this
870    */
871   public function setUrlGenerator(UrlGeneratorInterface $url_generator = NULL) {
872     $this->urlGenerator = $url_generator;
873     $this->internalPath = NULL;
874     return $this;
875   }
876
877   /**
878    * Sets the unrouted URL assembler.
879    *
880    * @param \Drupal\Core\Utility\UnroutedUrlAssemblerInterface $url_assembler
881    *   The unrouted URL assembler.
882    *
883    * @return $this
884    */
885   public function setUnroutedUrlAssembler(UnroutedUrlAssemblerInterface $url_assembler) {
886     $this->urlAssembler = $url_assembler;
887     return $this;
888   }
889
890 }