Chris@0: requestStack = $request_stack; Chris@0: $this->pathProcessor = $path_processor; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: * Chris@0: * This is a helper function that calls buildExternalUrl() or buildLocalUrl() Chris@0: * based on a check of whether the path is a valid external URL. Chris@0: */ Chris@0: public function assemble($uri, array $options = [], $collect_bubbleable_metadata = FALSE) { Chris@0: // Note that UrlHelper::isExternal will return FALSE if the $uri has a Chris@0: // disallowed protocol. This is later made safe since we always add at Chris@0: // least a leading slash. Chris@0: if (parse_url($uri, PHP_URL_SCHEME) === 'base') { Chris@0: return $this->buildLocalUrl($uri, $options, $collect_bubbleable_metadata); Chris@0: } Chris@0: elseif (UrlHelper::isExternal($uri)) { Chris@0: // UrlHelper::isExternal() only returns true for safe protocols. Chris@0: return $this->buildExternalUrl($uri, $options, $collect_bubbleable_metadata); Chris@0: } Chris@0: throw new \InvalidArgumentException("The URI '$uri' is invalid. You must use a valid URI scheme. Use base: for a path, e.g., to a Drupal file that needs the base path. Do not use this for internal paths controlled by Drupal."); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: protected function buildExternalUrl($uri, array $options = [], $collect_bubbleable_metadata = FALSE) { Chris@0: $this->addOptionDefaults($options); Chris@16: // Split off the query & fragment. Chris@16: $parsed = UrlHelper::parse($uri); Chris@16: $uri = $parsed['path']; Chris@16: Chris@16: $parsed += ['query' => []]; Chris@16: $options += ['query' => []]; Chris@16: Chris@16: $options['query'] = NestedArray::mergeDeep($parsed['query'], $options['query']); Chris@16: Chris@16: if ($parsed['fragment'] && !$options['fragment']) { Chris@16: $options['fragment'] = '#' . $parsed['fragment']; Chris@0: } Chris@0: Chris@0: if (isset($options['https'])) { Chris@0: if ($options['https'] === TRUE) { Chris@0: $uri = str_replace('http://', 'https://', $uri); Chris@0: } Chris@0: elseif ($options['https'] === FALSE) { Chris@0: $uri = str_replace('https://', 'http://', $uri); Chris@0: } Chris@0: } Chris@0: // Append the query. Chris@0: if ($options['query']) { Chris@16: $uri .= '?' . UrlHelper::buildQuery($options['query']); Chris@0: } Chris@0: // Reassemble. Chris@0: $url = $uri . $options['fragment']; Chris@0: return $collect_bubbleable_metadata ? (new GeneratedUrl())->setGeneratedUrl($url) : $url; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: protected function buildLocalUrl($uri, array $options = [], $collect_bubbleable_metadata = FALSE) { Chris@0: $generated_url = $collect_bubbleable_metadata ? new GeneratedUrl() : NULL; Chris@0: Chris@0: $this->addOptionDefaults($options); Chris@0: $request = $this->requestStack->getCurrentRequest(); Chris@0: Chris@0: // Remove the base: scheme. Chris@0: // @todo Consider using a class constant for this in Chris@0: // https://www.drupal.org/node/2417459 Chris@0: $uri = substr($uri, 5); Chris@0: Chris@0: // Allow (outbound) path processing, if needed. A valid use case is the path Chris@0: // alias overview form: Chris@0: // @see \Drupal\path\Controller\PathController::adminOverview(). Chris@0: if (!empty($options['path_processing'])) { Chris@0: // Do not pass the request, since this is a special case and we do not Chris@0: // want to include e.g. the request language in the processing. Chris@0: $uri = $this->pathProcessor->processOutbound($uri, $options, NULL, $generated_url); Chris@0: } Chris@0: // Strip leading slashes from internal paths to prevent them becoming Chris@0: // external URLs without protocol. /example.com should not be turned into Chris@0: // //example.com. Chris@0: $uri = ltrim($uri, '/'); Chris@0: Chris@0: // Add any subdirectory where Drupal is installed. Chris@0: $current_base_path = $request->getBasePath() . '/'; Chris@0: Chris@0: if ($options['absolute']) { Chris@0: $current_base_url = $request->getSchemeAndHttpHost() . $current_base_path; Chris@0: if (isset($options['https'])) { Chris@0: if (!empty($options['https'])) { Chris@0: $base = str_replace('http://', 'https://', $current_base_url); Chris@0: $options['absolute'] = TRUE; Chris@0: } Chris@0: else { Chris@0: $base = str_replace('https://', 'http://', $current_base_url); Chris@0: $options['absolute'] = TRUE; Chris@0: } Chris@0: } Chris@0: else { Chris@0: $base = $current_base_url; Chris@0: } Chris@0: if ($collect_bubbleable_metadata) { Chris@0: $generated_url->addCacheContexts(['url.site']); Chris@0: } Chris@0: } Chris@0: else { Chris@0: $base = $current_base_path; Chris@0: } Chris@0: Chris@0: $prefix = empty($uri) ? rtrim($options['prefix'], '/') : $options['prefix']; Chris@0: Chris@0: $uri = str_replace('%2F', '/', rawurlencode($prefix . $uri)); Chris@0: $query = $options['query'] ? ('?' . UrlHelper::buildQuery($options['query'])) : ''; Chris@0: $url = $base . $options['script'] . $uri . $query . $options['fragment']; Chris@0: return $collect_bubbleable_metadata ? $generated_url->setGeneratedUrl($url) : $url; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Merges in default defaults Chris@0: * Chris@0: * @param array $options Chris@0: * The options to merge in the defaults. Chris@0: */ Chris@0: protected function addOptionDefaults(array &$options) { Chris@0: $request = $this->requestStack->getCurrentRequest(); Chris@0: $current_base_path = $request->getBasePath() . '/'; Chris@0: $current_script_path = ''; Chris@0: $base_path_with_script = $request->getBaseUrl(); Chris@0: Chris@0: // If the current request was made with the script name (eg, index.php) in Chris@0: // it, then extract it, making sure the leading / is gone, and a trailing / Chris@0: // is added, to allow simple string concatenation with other parts. Chris@0: if (!empty($base_path_with_script)) { Chris@0: $script_name = $request->getScriptName(); Chris@0: if (strpos($base_path_with_script, $script_name) !== FALSE) { Chris@0: $current_script_path = ltrim(substr($script_name, strlen($current_base_path)), '/') . '/'; Chris@0: } Chris@0: } Chris@0: Chris@0: // Merge in defaults. Chris@0: $options += [ Chris@0: 'fragment' => '', Chris@0: 'query' => [], Chris@0: 'absolute' => FALSE, Chris@0: 'prefix' => '', Chris@0: 'script' => $current_script_path, Chris@0: ]; Chris@0: Chris@0: if (isset($options['fragment']) && $options['fragment'] !== '') { Chris@0: $options['fragment'] = '#' . $options['fragment']; Chris@0: } Chris@0: } Chris@0: Chris@0: }