From d1532c9d605a53f856bf1e22c0f6ef72dde338c5 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Thu, 16 Jan 2020 00:42:55 -0800 Subject: [PATCH] Allow most values of $civicrm_paths['XXX']['url'] to be relative Overview -------- The `$civicrm_paths` variable allows a sysadmin to override various path and URL computations. ```php $civicrm_paths['civicrm.packages']['url'] = 'https://example.com/libraries/civicrm/packages'; ``` The variable was originally tested with absolute URLs, and the subsequent examples/docs use absolute URLs (https://docs.civicrm.org/dev/en/latest/framework/filesystem/). These values are used to generate addresses, as in: ```php $abs = Civi::paths()->getUrl('[civicrm.packages]/foo.js', 'absolute'); $rel = Civi::paths()->getUrl('[civicrm.packages]/foo.js', 'relative'); ``` The patch allows more values in `$civicrm_paths` while ensuring that `getUrl()` works as expected. Before ------ The `getUrl()` requests only behave correctly if the override is an absolute URL - not if it's relative. After ----- The `getUrl()` requests behave correctly if the override is either an absolute URL or a relative URL. ```php $civicrm_paths['civicrm.packages']['url'] = 'https://example.com/libraries/civicrm/packages'; $civicrm_paths['civicrm.packages']['url'] = '/libraries/civicrm/packages'; ``` Comments -------- * `toAbsoluteUrl()` needs a base to prepend. I initially used `HTTP_HOST` but switched to `cms.root`, but correctly inferring scheme and host and port and httpd prefixes would be more complex - esp for background/CLI jobs. Using `cms.root` as the base is simpler. * It's tempting to allow recursive variables. But it's not actually needed for my purposes, and it would add complexity/maintenance. If it's really needed, one could update `toAbsoluteUrl()` to quickly check for variables (`$url[0] === '['`) and then evaluate them. But for now... I think the simpler format is fine. --- Civi/Core/Paths.php | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Civi/Core/Paths.php b/Civi/Core/Paths.php index a4ab40c220..e2e1ff62ea 100644 --- a/Civi/Core/Paths.php +++ b/Civi/Core/Paths.php @@ -146,6 +146,10 @@ class Paths { if (isset($GLOBALS['civicrm_paths'][$name])) { $this->variables[$name] = array_merge($this->variables[$name], $GLOBALS['civicrm_paths'][$name]); } + if (isset($this->variables[$name]['url'])) { + // Typical behavior is to return an absolute URL. If an admin has put an override that's site-relative, then convert. + $this->variables[$name]['url'] = $this->toAbsoluteUrl($this->variables[$name]['url'], $name); + } } if (!isset($this->variables[$name][$attr])) { throw new \RuntimeException("Cannot resolve path using \"$name.$attr\""); @@ -153,6 +157,37 @@ class Paths { return $this->variables[$name][$attr]; } + /** + * @param string $url + * Ex: 'https://example.com:8000/foobar' or '/foobar' + * @param string $for + * Ex: 'civicrm.root' or 'civicrm.packages' + * @return string + */ + private function toAbsoluteUrl($url, $for) { + if (!$url) { + return $url; + } + elseif ($url[0] === '/') { + // Relative URL interpretation + if ($for === 'cms.root') { + throw new \RuntimeException('Invalid configuration: the [cms.root] path must be an absolute URL'); + } + $cmsUrl = rtrim($this->getVariable('cms.root', 'url'), '/'); + // The norms for relative URLs dictate: + // Single-slash: "/sub/dir" or "/" (domain-relative) + // Double-slash: "//example.com/sub/dir" (same-scheme) + $prefix = ($url === '/' || $url[1] !== '/') + ? $cmsUrl + : (parse_url($cmsUrl, PHP_URL_SCHEME) . ':'); + return $prefix . $url; + } + else { + // Assume this is an absolute URL, as in the past ('_h_ttp://'). + return $url; + } + } + /** * Does the variable exist. * -- 2.25.1