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