Chris@0: routeName = $route_name; Chris@0: $this->routeParameters = $route_parameters; Chris@0: $this->options = $options; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a new Url object for a URL that has a Drupal route. Chris@0: * Chris@0: * This method is for URLs that have Drupal routes (that is, most pages Chris@0: * generated by Drupal). For non-routed local URIs relative to the base Chris@0: * path (like robots.txt) use Url::fromUri() with the base: scheme. Chris@0: * Chris@0: * @param string $route_name Chris@0: * The name of the route Chris@0: * @param array $route_parameters Chris@0: * (optional) An associative array of route parameter names and values. Chris@0: * @param array $options Chris@0: * See \Drupal\Core\Url::fromUri() for details. Chris@0: * Chris@0: * @return \Drupal\Core\Url Chris@0: * A new Url object for a routed (internal to Drupal) URL. Chris@0: * Chris@0: * @see \Drupal\Core\Url::fromUserInput() Chris@0: * @see \Drupal\Core\Url::fromUri() Chris@0: */ Chris@0: public static function fromRoute($route_name, $route_parameters = [], $options = []) { Chris@0: return new static($route_name, $route_parameters, $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a new URL object from a route match. Chris@0: * Chris@0: * @param \Drupal\Core\Routing\RouteMatchInterface $route_match Chris@0: * The route match. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public static function fromRouteMatch(RouteMatchInterface $route_match) { Chris@0: if ($route_match->getRouteObject()) { Chris@0: return new static($route_match->getRouteName(), $route_match->getRawParameters()->all()); Chris@0: } Chris@0: else { Chris@0: throw new \InvalidArgumentException('Route required'); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a Url object for a relative URI reference submitted by user input. Chris@0: * Chris@0: * Use this method to create a URL for user-entered paths that may or may not Chris@0: * correspond to a valid Drupal route. Chris@0: * Chris@0: * @param string $user_input Chris@0: * User input for a link or path. The first character must be one of the Chris@0: * following characters: Chris@0: * - '/': A path within the current site. This path might be to a Drupal Chris@0: * route (e.g., '/admin'), to a file (e.g., '/README.txt'), or to Chris@0: * something processed by a non-Drupal script (e.g., Chris@0: * '/not/a/drupal/page'). If the path matches a Drupal route, then the Chris@0: * URL generation will include Drupal's path processors (e.g., Chris@0: * language-prefixing and aliasing). Otherwise, the URL generation will Chris@0: * just append the passed-in path to Drupal's base path. Chris@0: * - '?': A query string for the current page or resource. Chris@0: * - '#': A fragment (jump-link) on the current page or resource. Chris@0: * This helps reduce ambiguity for user-entered links and paths, and Chris@0: * supports user interfaces where users may normally use auto-completion Chris@0: * to search for existing resources, but also may type one of these Chris@0: * characters to link to (e.g.) a specific path on the site. Chris@0: * (With regard to the URI specification, the user input is treated as a Chris@0: * @link https://tools.ietf.org/html/rfc3986#section-4.2 relative URI reference @endlink Chris@0: * where the relative part is of type Chris@0: * @link https://tools.ietf.org/html/rfc3986#section-3.3 path-abempty @endlink.) Chris@0: * @param array $options Chris@0: * (optional) An array of options. See Url::fromUri() for details. Chris@0: * Chris@0: * @return static Chris@0: * A new Url object based on user input. Chris@0: * Chris@0: * @throws \InvalidArgumentException Chris@0: * Thrown when the user input does not begin with one of the following Chris@0: * characters: '/', '?', or '#'. Chris@0: */ Chris@0: public static function fromUserInput($user_input, $options = []) { Chris@0: // Ensuring one of these initial characters also enforces that what is Chris@0: // passed is a relative URI reference rather than an absolute URI, Chris@0: // because these are URI reserved characters that a scheme name may not Chris@0: // start with. Chris@0: if ((strpos($user_input, '/') !== 0) && (strpos($user_input, '#') !== 0) && (strpos($user_input, '?') !== 0)) { Chris@0: throw new \InvalidArgumentException("The user-entered string '$user_input' must begin with a '/', '?', or '#'."); Chris@0: } Chris@0: Chris@0: // fromUri() requires an absolute URI, so prepend the appropriate scheme Chris@0: // name. Chris@0: return static::fromUri('internal:' . $user_input, $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a new Url object from a URI. Chris@0: * Chris@0: * This method is for generating URLs for URIs that: Chris@0: * - do not have Drupal routes: both external URLs and unrouted local URIs Chris@0: * like base:robots.txt Chris@0: * - do have a Drupal route but have a custom scheme to simplify linking. Chris@0: * Currently, there is only the entity: scheme (This allows URIs of the Chris@0: * form entity:{entity_type}/{entity_id}. For example: entity:node/1 Chris@0: * resolves to the entity.node.canonical route with a node parameter of 1.) Chris@0: * Chris@0: * For URLs that have Drupal routes (that is, most pages generated by Drupal), Chris@0: * use Url::fromRoute(). Chris@0: * Chris@0: * @param string $uri Chris@0: * The URI of the resource including the scheme. For user input that may Chris@0: * correspond to a Drupal route, use internal: for the scheme. For paths Chris@0: * that are known not to be handled by the Drupal routing system (such as Chris@0: * static files), use base: for the scheme to get a link relative to the Chris@0: * Drupal base path (like the HTML element). For a link to an entity Chris@0: * you may use entity:{entity_type}/{entity_id} URIs. The internal: scheme Chris@0: * should be avoided except when processing actual user input that may or Chris@0: * may not correspond to a Drupal route. Normally use Url::fromRoute() for Chris@0: * code linking to any any Drupal page. Chris@0: * @param array $options Chris@0: * (optional) An associative array of additional URL options, with the Chris@0: * following elements: Chris@0: * - 'query': An array of query key/value-pairs (without any URL-encoding) Chris@0: * to append to the URL. Chris@0: * - 'fragment': A fragment identifier (named anchor) to append to the URL. Chris@0: * Do not include the leading '#' character. Chris@0: * - 'absolute': Defaults to FALSE. Whether to force the output to be an Chris@0: * absolute link (beginning with http:). Useful for links that will be Chris@0: * displayed outside the site, such as in an RSS feed. Chris@0: * - 'attributes': An associative array of HTML attributes that will be Chris@0: * added to the anchor tag if you use the \Drupal\Core\Link class to make Chris@0: * the link. Chris@0: * - 'language': An optional language object used to look up the alias Chris@0: * for the URL. If $options['language'] is omitted, it defaults to the Chris@0: * current language for the language type LanguageInterface::TYPE_URL. Chris@0: * - 'https': Whether this URL should point to a secure location. If not Chris@0: * defined, the current scheme is used, so the user stays on HTTP or HTTPS Chris@0: * respectively. TRUE enforces HTTPS and FALSE enforces HTTP. Chris@0: * Chris@0: * @return \Drupal\Core\Url Chris@0: * A new Url object with properties depending on the URI scheme. Call the Chris@0: * access() method on this to do access checking. Chris@0: * Chris@0: * @throws \InvalidArgumentException Chris@0: * Thrown when the passed in path has no scheme. Chris@0: * Chris@0: * @see \Drupal\Core\Url::fromRoute() Chris@0: * @see \Drupal\Core\Url::fromUserInput() Chris@0: */ Chris@0: public static function fromUri($uri, $options = []) { Chris@0: // parse_url() incorrectly parses base:number/... as hostname:port/... Chris@0: // and not the scheme. Prevent that by prefixing the path with a slash. Chris@0: if (preg_match('/^base:\d/', $uri)) { Chris@0: $uri = str_replace('base:', 'base:/', $uri); Chris@0: } Chris@0: $uri_parts = parse_url($uri); Chris@0: if ($uri_parts === FALSE) { Chris@0: throw new \InvalidArgumentException("The URI '$uri' is malformed."); Chris@0: } Chris@0: // We support protocol-relative URLs. Chris@0: if (strpos($uri, '//') === 0) { Chris@0: $uri_parts['scheme'] = ''; Chris@0: } Chris@0: elseif (empty($uri_parts['scheme'])) { Chris@0: throw new \InvalidArgumentException("The URI '$uri' is invalid. You must use a valid URI scheme."); Chris@0: } Chris@0: $uri_parts += ['path' => '']; Chris@0: // Discard empty fragment in $options for consistency with parse_url(). Chris@0: if (isset($options['fragment']) && strlen($options['fragment']) == 0) { Chris@0: unset($options['fragment']); Chris@0: } Chris@0: // Extract query parameters and fragment and merge them into $uri_options, Chris@0: // but preserve the original $options for the fallback case. Chris@0: $uri_options = $options; Chris@0: if (isset($uri_parts['fragment'])) { Chris@0: $uri_options += ['fragment' => $uri_parts['fragment']]; Chris@0: unset($uri_parts['fragment']); Chris@0: } Chris@0: Chris@0: if (!empty($uri_parts['query'])) { Chris@0: $uri_query = []; Chris@0: parse_str($uri_parts['query'], $uri_query); Chris@0: $uri_options['query'] = isset($uri_options['query']) ? $uri_options['query'] + $uri_query : $uri_query; Chris@0: unset($uri_parts['query']); Chris@0: } Chris@0: Chris@0: if ($uri_parts['scheme'] === 'entity') { Chris@0: $url = static::fromEntityUri($uri_parts, $uri_options, $uri); Chris@0: } Chris@0: elseif ($uri_parts['scheme'] === 'internal') { Chris@0: $url = static::fromInternalUri($uri_parts, $uri_options); Chris@0: } Chris@0: elseif ($uri_parts['scheme'] === 'route') { Chris@0: $url = static::fromRouteUri($uri_parts, $uri_options, $uri); Chris@0: } Chris@0: else { Chris@0: $url = new static($uri, [], $options); Chris@0: if ($uri_parts['scheme'] !== 'base') { Chris@0: $url->external = TRUE; Chris@0: $url->setOption('external', TRUE); Chris@0: } Chris@0: $url->setUnrouted(); Chris@0: } Chris@0: Chris@0: return $url; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Create a new Url object for entity URIs. Chris@0: * Chris@0: * @param array $uri_parts Chris@0: * Parts from an URI of the form entity:{entity_type}/{entity_id} as from Chris@0: * parse_url(). Chris@0: * @param array $options Chris@0: * An array of options, see \Drupal\Core\Url::fromUri() for details. Chris@0: * @param string $uri Chris@0: * The original entered URI. Chris@0: * Chris@0: * @return \Drupal\Core\Url Chris@0: * A new Url object for an entity's canonical route. Chris@0: * Chris@0: * @throws \InvalidArgumentException Chris@0: * Thrown if the entity URI is invalid. Chris@0: */ Chris@0: protected static function fromEntityUri(array $uri_parts, array $options, $uri) { Chris@0: list($entity_type_id, $entity_id) = explode('/', $uri_parts['path'], 2); Chris@0: if ($uri_parts['scheme'] != 'entity' || $entity_id === '') { Chris@0: 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: } Chris@0: Chris@0: return new static("entity.$entity_type_id.canonical", [$entity_type_id => $entity_id], $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a new Url object for 'internal:' URIs. Chris@0: * Chris@0: * Important note: the URI minus the scheme can NOT simply be validated by a Chris@0: * \Drupal\Core\Path\PathValidatorInterface implementation. The semantics of Chris@0: * the 'internal:' URI scheme are different: Chris@0: * - PathValidatorInterface accepts paths without a leading slash (e.g. Chris@0: * 'node/add') as well as 2 special paths: '' and '', which are Chris@0: * mapped to the correspondingly named routes. Chris@0: * - 'internal:' URIs store paths with a leading slash that represents the Chris@0: * root — i.e. the front page — (e.g. 'internal:/node/add'), and doesn't Chris@0: * have any exceptions. Chris@0: * Chris@0: * To clarify, a few examples of path plus corresponding 'internal:' URI: Chris@0: * - 'node/add' -> 'internal:/node/add' Chris@0: * - 'node/add?foo=bar' -> 'internal:/node/add?foo=bar' Chris@0: * - 'node/add#kitten' -> 'internal:/node/add#kitten' Chris@0: * - '' -> 'internal:/' Chris@0: * - 'foo=bar' -> 'internal:/?foo=bar' Chris@0: * - '#kitten' -> 'internal:/#kitten' Chris@0: * - '' -> 'internal:' Chris@0: * - 'foo=bar' -> 'internal:?foo=bar' Chris@0: * - '#kitten' -> 'internal:#kitten' Chris@0: * Chris@0: * Therefore, when using a PathValidatorInterface to validate 'internal:' Chris@0: * URIs, we must map: Chris@0: * - 'internal:' (path component is '') to the special '' path Chris@0: * - 'internal:/' (path component is '/') to the special '' path Chris@0: * - 'internal:/some-path' (path component is '/some-path') to 'some-path' Chris@0: * Chris@0: * @param array $uri_parts Chris@0: * Parts from an URI of the form internal:{path} as from parse_url(). Chris@0: * @param array $options Chris@0: * An array of options, see \Drupal\Core\Url::fromUri() for details. Chris@0: * Chris@0: * @return \Drupal\Core\Url Chris@0: * A new Url object for a 'internal:' URI. Chris@0: * Chris@0: * @throws \InvalidArgumentException Chris@0: * Thrown when the URI's path component doesn't have a leading slash. Chris@0: */ Chris@0: protected static function fromInternalUri(array $uri_parts, array $options) { Chris@0: // Both PathValidator::getUrlIfValidWithoutAccessCheck() and 'base:' URIs Chris@0: // only accept/contain paths without a leading slash, unlike 'internal:' Chris@0: // URIs, for which the leading slash means "relative to Drupal root" and Chris@0: // "relative to Symfony app root" (just like in Symfony/Drupal 8 routes). Chris@0: if (empty($uri_parts['path'])) { Chris@0: $uri_parts['path'] = ''; Chris@0: } Chris@0: elseif ($uri_parts['path'] === '/') { Chris@0: $uri_parts['path'] = ''; Chris@0: } Chris@0: else { Chris@0: if ($uri_parts['path'][0] !== '/') { Chris@0: 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: } Chris@0: // Remove the leading slash. Chris@0: $uri_parts['path'] = substr($uri_parts['path'], 1); Chris@0: Chris@0: if (UrlHelper::isExternal($uri_parts['path'])) { Chris@0: 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: } Chris@0: } Chris@0: Chris@0: $url = \Drupal::pathValidator() Chris@0: ->getUrlIfValidWithoutAccessCheck($uri_parts['path']) ?: static::fromUri('base:' . $uri_parts['path'], $options); Chris@0: // Allow specifying additional options. Chris@0: $url->setOptions($options + $url->getOptions()); Chris@0: Chris@0: return $url; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a new Url object for 'route:' URIs. Chris@0: * Chris@0: * @param array $uri_parts Chris@0: * Parts from an URI of the form route:{route_name};{route_parameters} as Chris@0: * from parse_url(), where the path is the route name optionally followed by Chris@0: * a ";" followed by route parameters in key=value format with & separators. Chris@0: * @param array $options Chris@0: * An array of options, see \Drupal\Core\Url::fromUri() for details. Chris@0: * @param string $uri Chris@0: * The original passed in URI. Chris@0: * Chris@0: * @return \Drupal\Core\Url Chris@0: * A new Url object for a 'route:' URI. Chris@0: * Chris@0: * @throws \InvalidArgumentException Chris@0: * Thrown when the route URI does not have a route name. Chris@0: */ Chris@0: protected static function fromRouteUri(array $uri_parts, array $options, $uri) { Chris@0: $route_parts = explode(';', $uri_parts['path'], 2); Chris@0: $route_name = $route_parts[0]; Chris@0: if ($route_name === '') { Chris@0: 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: } Chris@0: $route_parameters = []; Chris@0: if (!empty($route_parts[1])) { Chris@0: parse_str($route_parts[1], $route_parameters); Chris@0: } Chris@0: Chris@0: return new static($route_name, $route_parameters, $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the Url object matching a request. Chris@0: * Chris@0: * SECURITY NOTE: The request path is not checked to be valid and accessible Chris@0: * by the current user to allow storing and reusing Url objects by different Chris@0: * users. The 'path.validator' service getUrlIfValid() method should be used Chris@0: * instead of this one if validation and access check is desired. Otherwise, Chris@0: * 'access_manager' service checkNamedRoute() method should be used on the Chris@0: * router name and parameters stored in the Url object returned by this Chris@0: * method. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * A request object. Chris@0: * Chris@0: * @return static Chris@0: * A Url object. Warning: the object is created even if the current user Chris@0: * would get an access denied running the same request via the normal page Chris@0: * flow. Chris@0: * Chris@0: * @throws \Drupal\Core\Routing\MatchingRouteNotFoundException Chris@0: * Thrown when the request cannot be matched. Chris@0: */ Chris@0: public static function createFromRequest(Request $request) { Chris@0: // We use the router without access checks because URL objects might be Chris@0: // created and stored for different users. Chris@0: $result = \Drupal::service('router.no_access_checks')->matchRequest($request); Chris@0: $route_name = $result[RouteObjectInterface::ROUTE_NAME]; Chris@0: $route_parameters = $result['_raw_variables']->all(); Chris@0: return new static($route_name, $route_parameters); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets this Url to encapsulate an unrouted URI. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: protected function setUnrouted() { Chris@0: $this->unrouted = TRUE; Chris@0: // What was passed in as the route name is actually the URI. Chris@0: // @todo Consider fixing this in https://www.drupal.org/node/2346787. Chris@0: $this->uri = $this->routeName; Chris@0: // Set empty route name and parameters. Chris@0: $this->routeName = NULL; Chris@0: $this->routeParameters = []; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Generates a URI string that represents the data in the Url object. Chris@0: * Chris@0: * The URI will typically have the scheme of route: even if the object was Chris@0: * constructed using an entity: or internal: scheme. A internal: URI that Chris@0: * does not match a Drupal route with be returned here with the base: scheme, Chris@0: * and external URLs will be returned in their original form. Chris@0: * Chris@0: * @return string Chris@0: * A URI representation of the Url object data. Chris@0: */ Chris@0: public function toUriString() { Chris@0: if ($this->isRouted()) { Chris@0: $uri = 'route:' . $this->routeName; Chris@0: if ($this->routeParameters) { Chris@0: $uri .= ';' . UrlHelper::buildQuery($this->routeParameters); Chris@0: } Chris@0: } Chris@0: else { Chris@0: $uri = $this->uri; Chris@0: } Chris@0: $query = !empty($this->options['query']) ? ('?' . UrlHelper::buildQuery($this->options['query'])) : ''; Chris@0: $fragment = isset($this->options['fragment']) && strlen($this->options['fragment']) ? '#' . $this->options['fragment'] : ''; Chris@0: return $uri . $query . $fragment; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Indicates if this Url is external. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function isExternal() { Chris@0: return $this->external; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Indicates if this Url has a Drupal route. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function isRouted() { Chris@0: return !$this->unrouted; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the route name. Chris@0: * Chris@0: * @return string Chris@0: * Chris@0: * @throws \UnexpectedValueException. Chris@0: * If this is a URI with no corresponding route. Chris@0: */ Chris@0: public function getRouteName() { Chris@0: if ($this->unrouted) { Chris@0: throw new \UnexpectedValueException('External URLs do not have an internal route name.'); Chris@0: } Chris@0: Chris@0: return $this->routeName; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the route parameters. Chris@0: * Chris@0: * @return array Chris@0: * Chris@0: * @throws \UnexpectedValueException. Chris@0: * If this is a URI with no corresponding route. Chris@0: */ Chris@0: public function getRouteParameters() { Chris@0: if ($this->unrouted) { Chris@0: throw new \UnexpectedValueException('External URLs do not have internal route parameters.'); Chris@0: } Chris@0: Chris@0: return $this->routeParameters; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the route parameters. Chris@0: * Chris@0: * @param array $parameters Chris@0: * The array of parameters. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @throws \UnexpectedValueException. Chris@0: * If this is a URI with no corresponding route. Chris@0: */ Chris@0: public function setRouteParameters($parameters) { Chris@0: if ($this->unrouted) { Chris@0: throw new \UnexpectedValueException('External URLs do not have route parameters.'); Chris@0: } Chris@0: $this->routeParameters = $parameters; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets a specific route parameter. Chris@0: * Chris@0: * @param string $key Chris@0: * The key of the route parameter. Chris@0: * @param mixed $value Chris@0: * The route parameter. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @throws \UnexpectedValueException. Chris@0: * If this is a URI with no corresponding route. Chris@0: */ Chris@0: public function setRouteParameter($key, $value) { Chris@0: if ($this->unrouted) { Chris@0: throw new \UnexpectedValueException('External URLs do not have route parameters.'); Chris@0: } Chris@0: $this->routeParameters[$key] = $value; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the URL options. Chris@0: * Chris@0: * @return array Chris@0: * The array of options. See \Drupal\Core\Url::fromUri() for details on what Chris@0: * it contains. Chris@0: */ Chris@0: public function getOptions() { Chris@0: return $this->options; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets a specific option. Chris@0: * Chris@0: * See \Drupal\Core\Url::fromUri() for details on the options. Chris@0: * Chris@0: * @param string $name Chris@0: * The name of the option. Chris@0: * Chris@0: * @return mixed Chris@0: * The value for a specific option, or NULL if it does not exist. Chris@0: */ Chris@0: public function getOption($name) { Chris@0: if (!isset($this->options[$name])) { Chris@0: return NULL; Chris@0: } Chris@0: Chris@0: return $this->options[$name]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the URL options. Chris@0: * Chris@0: * @param array $options Chris@0: * The array of options. See \Drupal\Core\Url::fromUri() for details on what Chris@0: * it contains. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function setOptions($options) { Chris@0: $this->options = $options; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets a specific option. Chris@0: * Chris@0: * See \Drupal\Core\Url::fromUri() for details on the options. Chris@0: * Chris@0: * @param string $name Chris@0: * The name of the option. Chris@0: * @param mixed $value Chris@0: * The option value. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function setOption($name, $value) { Chris@0: $this->options[$name] = $value; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Merges the URL options with any currently set. Chris@0: * Chris@0: * In the case of conflict with existing options, the new options will replace Chris@0: * the existing options. Chris@0: * Chris@0: * @param array $options Chris@0: * The array of options. See \Drupal\Core\Url::fromUri() for details on what Chris@0: * it contains. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function mergeOptions($options) { Chris@0: $this->options = NestedArray::mergeDeep($this->options, $options); Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the URI value for this Url object. Chris@0: * Chris@0: * Only to be used if self::$unrouted is TRUE. Chris@0: * Chris@0: * @return string Chris@0: * A URI not connected to a route. May be an external URL. Chris@0: * Chris@0: * @throws \UnexpectedValueException Chris@0: * Thrown when the URI was requested for a routed URL. Chris@0: */ Chris@0: public function getUri() { Chris@0: if (!$this->unrouted) { Chris@0: throw new \UnexpectedValueException('This URL has a Drupal route, so the canonical form is not a URI.'); Chris@0: } Chris@0: Chris@0: return $this->uri; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the value of the absolute option for this Url. Chris@0: * Chris@0: * @param bool $absolute Chris@0: * (optional) Whether to make this Url absolute or not. Defaults to TRUE. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function setAbsolute($absolute = TRUE) { Chris@0: $this->options['absolute'] = $absolute; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Generates the string URL representation for this Url object. Chris@0: * Chris@0: * For an external URL, the string will contain the input plus any query Chris@0: * string or fragment specified by the options array. Chris@0: * Chris@0: * If this Url object was constructed from a Drupal route or from an internal Chris@0: * URI (URIs using the internal:, base:, or entity: schemes), the returned Chris@0: * string will either be a relative URL like /node/1 or an absolute URL like Chris@0: * http://example.com/node/1 depending on the options array, plus any Chris@0: * specified query string or fragment. Chris@0: * Chris@0: * @param bool $collect_bubbleable_metadata Chris@0: * (optional) Defaults to FALSE. When TRUE, both the generated URL and its Chris@0: * associated bubbleable metadata are returned. Chris@0: * Chris@0: * @return string|\Drupal\Core\GeneratedUrl Chris@0: * A string URL. Chris@0: * When $collect_bubbleable_metadata is TRUE, a GeneratedUrl object is Chris@0: * returned, containing the generated URL plus bubbleable metadata. Chris@0: */ Chris@0: public function toString($collect_bubbleable_metadata = FALSE) { Chris@0: if ($this->unrouted) { Chris@0: return $this->unroutedUrlAssembler()->assemble($this->getUri(), $this->getOptions(), $collect_bubbleable_metadata); Chris@0: } Chris@0: Chris@0: return $this->urlGenerator()->generateFromRoute($this->getRouteName(), $this->getRouteParameters(), $this->getOptions(), $collect_bubbleable_metadata); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the route information for a render array. Chris@0: * Chris@0: * @return array Chris@0: * An associative array suitable for a render array. Chris@0: */ Chris@0: public function toRenderArray() { Chris@0: $render_array = [ Chris@0: '#url' => $this, Chris@0: '#options' => $this->getOptions(), Chris@0: ]; Chris@0: if (!$this->unrouted) { Chris@0: $render_array['#access_callback'] = [get_class(), 'renderAccess']; Chris@0: } Chris@0: return $render_array; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the internal path (system path) for this route. Chris@0: * Chris@0: * This path will not include any prefixes, fragments, or query strings. Chris@0: * Chris@0: * @return string Chris@0: * The internal path for this route. Chris@0: * Chris@0: * @throws \UnexpectedValueException. Chris@0: * If this is a URI with no corresponding system path. Chris@0: */ Chris@0: public function getInternalPath() { Chris@0: if ($this->unrouted) { Chris@0: throw new \UnexpectedValueException('Unrouted URIs do not have internal representations.'); Chris@0: } Chris@0: Chris@0: if (!isset($this->internalPath)) { Chris@0: $this->internalPath = $this->urlGenerator()->getPathFromRoute($this->getRouteName(), $this->getRouteParameters()); Chris@0: } Chris@0: return $this->internalPath; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks this Url object against applicable access check services. Chris@0: * Chris@0: * Determines whether the route is accessible or not. Chris@0: * Chris@0: * @param \Drupal\Core\Session\AccountInterface $account Chris@0: * (optional) Run access checks for this account. Defaults to the current Chris@0: * user. Chris@0: * Chris@0: * @return bool Chris@0: * Returns TRUE if the user has access to the url, otherwise FALSE. Chris@0: */ Chris@0: public function access(AccountInterface $account = NULL) { Chris@0: if ($this->isRouted()) { Chris@0: return $this->accessManager()->checkNamedRoute($this->getRouteName(), $this->getRouteParameters(), $account); Chris@0: } Chris@0: return TRUE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks a Url render element against applicable access check services. Chris@0: * Chris@0: * @param array $element Chris@0: * A render element as returned from \Drupal\Core\Url::toRenderArray(). Chris@0: * Chris@0: * @return bool Chris@0: * Returns TRUE if the current user has access to the url, otherwise FALSE. Chris@0: */ Chris@0: public static function renderAccess(array $element) { Chris@0: return $element['#url']->access(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return \Drupal\Core\Access\AccessManagerInterface Chris@0: */ Chris@0: protected function accessManager() { Chris@0: if (!isset($this->accessManager)) { Chris@0: $this->accessManager = \Drupal::service('access_manager'); Chris@0: } Chris@0: return $this->accessManager; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the URL generator. Chris@0: * Chris@0: * @return \Drupal\Core\Routing\UrlGeneratorInterface Chris@0: * The URL generator. Chris@0: */ Chris@0: protected function urlGenerator() { Chris@0: if (!$this->urlGenerator) { Chris@0: $this->urlGenerator = \Drupal::urlGenerator(); Chris@0: } Chris@0: return $this->urlGenerator; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the unrouted URL assembler for non-Drupal URLs. Chris@0: * Chris@0: * @return \Drupal\Core\Utility\UnroutedUrlAssemblerInterface Chris@0: * The unrouted URL assembler. Chris@0: */ Chris@0: protected function unroutedUrlAssembler() { Chris@0: if (!$this->urlAssembler) { Chris@0: $this->urlAssembler = \Drupal::service('unrouted_url_assembler'); Chris@0: } Chris@0: return $this->urlAssembler; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the URL generator. Chris@0: * Chris@0: * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator Chris@0: * (optional) The URL generator, specify NULL to reset it. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function setUrlGenerator(UrlGeneratorInterface $url_generator = NULL) { Chris@0: $this->urlGenerator = $url_generator; Chris@0: $this->internalPath = NULL; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the unrouted URL assembler. Chris@0: * Chris@0: * @param \Drupal\Core\Utility\UnroutedUrlAssemblerInterface $url_assembler Chris@0: * The unrouted URL assembler. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function setUnroutedUrlAssembler(UnroutedUrlAssemblerInterface $url_assembler) { Chris@0: $this->urlAssembler = $url_assembler; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: }