annotate core/lib/Drupal/Core/Url.php @ 19:fa3358dc1485 tip

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