annotate core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php @ 16:c2387f117808

Routine composer update
author Chris Cannam
date Tue, 10 Jul 2018 15:07:59 +0100
parents 4c8ae668cc8c
children 129ea1e6d783
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Utility;
Chris@0 4
Chris@16 5 use Drupal\Component\Utility\NestedArray;
Chris@0 6 use Drupal\Component\Utility\UrlHelper;
Chris@0 7 use Drupal\Core\GeneratedUrl;
Chris@0 8 use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
Chris@0 9 use Symfony\Component\HttpFoundation\RequestStack;
Chris@0 10
Chris@0 11 /**
Chris@0 12 * Provides a way to build external or non Drupal local domain URLs.
Chris@0 13 *
Chris@0 14 * It takes into account configured safe HTTP protocols.
Chris@0 15 */
Chris@0 16 class UnroutedUrlAssembler implements UnroutedUrlAssemblerInterface {
Chris@0 17
Chris@0 18 /**
Chris@0 19 * A request stack object.
Chris@0 20 *
Chris@0 21 * @var \Symfony\Component\HttpFoundation\RequestStack
Chris@0 22 */
Chris@0 23 protected $requestStack;
Chris@0 24
Chris@0 25 /**
Chris@0 26 * The outbound path processor.
Chris@0 27 *
Chris@0 28 * @var \Drupal\Core\PathProcessor\OutboundPathProcessorInterface
Chris@0 29 */
Chris@0 30 protected $pathProcessor;
Chris@0 31
Chris@0 32 /**
Chris@0 33 * Constructs a new unroutedUrlAssembler object.
Chris@0 34 *
Chris@0 35 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
Chris@0 36 * A request stack object.
Chris@0 37 * @param \Drupal\Core\PathProcessor\OutboundPathProcessorInterface $path_processor
Chris@0 38 * The output path processor.
Chris@0 39 * @param string[] $filter_protocols
Chris@0 40 * (optional) An array of protocols allowed for URL generation.
Chris@0 41 */
Chris@0 42 public function __construct(RequestStack $request_stack, OutboundPathProcessorInterface $path_processor, array $filter_protocols = ['http', 'https']) {
Chris@0 43 UrlHelper::setAllowedProtocols($filter_protocols);
Chris@0 44 $this->requestStack = $request_stack;
Chris@0 45 $this->pathProcessor = $path_processor;
Chris@0 46 }
Chris@0 47
Chris@0 48 /**
Chris@0 49 * {@inheritdoc}
Chris@0 50 *
Chris@0 51 * This is a helper function that calls buildExternalUrl() or buildLocalUrl()
Chris@0 52 * based on a check of whether the path is a valid external URL.
Chris@0 53 */
Chris@0 54 public function assemble($uri, array $options = [], $collect_bubbleable_metadata = FALSE) {
Chris@0 55 // Note that UrlHelper::isExternal will return FALSE if the $uri has a
Chris@0 56 // disallowed protocol. This is later made safe since we always add at
Chris@0 57 // least a leading slash.
Chris@0 58 if (parse_url($uri, PHP_URL_SCHEME) === 'base') {
Chris@0 59 return $this->buildLocalUrl($uri, $options, $collect_bubbleable_metadata);
Chris@0 60 }
Chris@0 61 elseif (UrlHelper::isExternal($uri)) {
Chris@0 62 // UrlHelper::isExternal() only returns true for safe protocols.
Chris@0 63 return $this->buildExternalUrl($uri, $options, $collect_bubbleable_metadata);
Chris@0 64 }
Chris@0 65 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 66 }
Chris@0 67
Chris@0 68 /**
Chris@0 69 * {@inheritdoc}
Chris@0 70 */
Chris@0 71 protected function buildExternalUrl($uri, array $options = [], $collect_bubbleable_metadata = FALSE) {
Chris@0 72 $this->addOptionDefaults($options);
Chris@16 73 // Split off the query & fragment.
Chris@16 74 $parsed = UrlHelper::parse($uri);
Chris@16 75 $uri = $parsed['path'];
Chris@16 76
Chris@16 77 $parsed += ['query' => []];
Chris@16 78 $options += ['query' => []];
Chris@16 79
Chris@16 80 $options['query'] = NestedArray::mergeDeep($parsed['query'], $options['query']);
Chris@16 81 ksort($options['query']);
Chris@16 82
Chris@16 83 if ($parsed['fragment'] && !$options['fragment']) {
Chris@16 84 $options['fragment'] = '#' . $parsed['fragment'];
Chris@0 85 }
Chris@0 86
Chris@0 87 if (isset($options['https'])) {
Chris@0 88 if ($options['https'] === TRUE) {
Chris@0 89 $uri = str_replace('http://', 'https://', $uri);
Chris@0 90 }
Chris@0 91 elseif ($options['https'] === FALSE) {
Chris@0 92 $uri = str_replace('https://', 'http://', $uri);
Chris@0 93 }
Chris@0 94 }
Chris@0 95 // Append the query.
Chris@0 96 if ($options['query']) {
Chris@16 97 $uri .= '?' . UrlHelper::buildQuery($options['query']);
Chris@0 98 }
Chris@0 99 // Reassemble.
Chris@0 100 $url = $uri . $options['fragment'];
Chris@0 101 return $collect_bubbleable_metadata ? (new GeneratedUrl())->setGeneratedUrl($url) : $url;
Chris@0 102 }
Chris@0 103
Chris@0 104 /**
Chris@0 105 * {@inheritdoc}
Chris@0 106 */
Chris@0 107 protected function buildLocalUrl($uri, array $options = [], $collect_bubbleable_metadata = FALSE) {
Chris@0 108 $generated_url = $collect_bubbleable_metadata ? new GeneratedUrl() : NULL;
Chris@0 109
Chris@0 110 $this->addOptionDefaults($options);
Chris@0 111 $request = $this->requestStack->getCurrentRequest();
Chris@0 112
Chris@0 113 // Remove the base: scheme.
Chris@0 114 // @todo Consider using a class constant for this in
Chris@0 115 // https://www.drupal.org/node/2417459
Chris@0 116 $uri = substr($uri, 5);
Chris@0 117
Chris@0 118 // Allow (outbound) path processing, if needed. A valid use case is the path
Chris@0 119 // alias overview form:
Chris@0 120 // @see \Drupal\path\Controller\PathController::adminOverview().
Chris@0 121 if (!empty($options['path_processing'])) {
Chris@0 122 // Do not pass the request, since this is a special case and we do not
Chris@0 123 // want to include e.g. the request language in the processing.
Chris@0 124 $uri = $this->pathProcessor->processOutbound($uri, $options, NULL, $generated_url);
Chris@0 125 }
Chris@0 126 // Strip leading slashes from internal paths to prevent them becoming
Chris@0 127 // external URLs without protocol. /example.com should not be turned into
Chris@0 128 // //example.com.
Chris@0 129 $uri = ltrim($uri, '/');
Chris@0 130
Chris@0 131 // Add any subdirectory where Drupal is installed.
Chris@0 132 $current_base_path = $request->getBasePath() . '/';
Chris@0 133
Chris@0 134 if ($options['absolute']) {
Chris@0 135 $current_base_url = $request->getSchemeAndHttpHost() . $current_base_path;
Chris@0 136 if (isset($options['https'])) {
Chris@0 137 if (!empty($options['https'])) {
Chris@0 138 $base = str_replace('http://', 'https://', $current_base_url);
Chris@0 139 $options['absolute'] = TRUE;
Chris@0 140 }
Chris@0 141 else {
Chris@0 142 $base = str_replace('https://', 'http://', $current_base_url);
Chris@0 143 $options['absolute'] = TRUE;
Chris@0 144 }
Chris@0 145 }
Chris@0 146 else {
Chris@0 147 $base = $current_base_url;
Chris@0 148 }
Chris@0 149 if ($collect_bubbleable_metadata) {
Chris@0 150 $generated_url->addCacheContexts(['url.site']);
Chris@0 151 }
Chris@0 152 }
Chris@0 153 else {
Chris@0 154 $base = $current_base_path;
Chris@0 155 }
Chris@0 156
Chris@0 157 $prefix = empty($uri) ? rtrim($options['prefix'], '/') : $options['prefix'];
Chris@0 158
Chris@0 159 $uri = str_replace('%2F', '/', rawurlencode($prefix . $uri));
Chris@0 160 $query = $options['query'] ? ('?' . UrlHelper::buildQuery($options['query'])) : '';
Chris@0 161 $url = $base . $options['script'] . $uri . $query . $options['fragment'];
Chris@0 162 return $collect_bubbleable_metadata ? $generated_url->setGeneratedUrl($url) : $url;
Chris@0 163 }
Chris@0 164
Chris@0 165 /**
Chris@0 166 * Merges in default defaults
Chris@0 167 *
Chris@0 168 * @param array $options
Chris@0 169 * The options to merge in the defaults.
Chris@0 170 */
Chris@0 171 protected function addOptionDefaults(array &$options) {
Chris@0 172 $request = $this->requestStack->getCurrentRequest();
Chris@0 173 $current_base_path = $request->getBasePath() . '/';
Chris@0 174 $current_script_path = '';
Chris@0 175 $base_path_with_script = $request->getBaseUrl();
Chris@0 176
Chris@0 177 // If the current request was made with the script name (eg, index.php) in
Chris@0 178 // it, then extract it, making sure the leading / is gone, and a trailing /
Chris@0 179 // is added, to allow simple string concatenation with other parts.
Chris@0 180 if (!empty($base_path_with_script)) {
Chris@0 181 $script_name = $request->getScriptName();
Chris@0 182 if (strpos($base_path_with_script, $script_name) !== FALSE) {
Chris@0 183 $current_script_path = ltrim(substr($script_name, strlen($current_base_path)), '/') . '/';
Chris@0 184 }
Chris@0 185 }
Chris@0 186
Chris@0 187 // Merge in defaults.
Chris@0 188 $options += [
Chris@0 189 'fragment' => '',
Chris@0 190 'query' => [],
Chris@0 191 'absolute' => FALSE,
Chris@0 192 'prefix' => '',
Chris@0 193 'script' => $current_script_path,
Chris@0 194 ];
Chris@0 195
Chris@0 196 if (isset($options['fragment']) && $options['fragment'] !== '') {
Chris@0 197 $options['fragment'] = '#' . $options['fragment'];
Chris@0 198 }
Chris@0 199 }
Chris@0 200
Chris@0 201 }