comparison core/lib/Drupal/Core/Url.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
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 }