8 * This paths class translates path-expressions into local file paths and
9 * URLs. Path-expressions may take a few forms:
11 * - Paths and URLs may use a variable prefix. For example, '[civicrm.files]/upload'
12 * - Paths and URLS may be absolute.
13 * - Paths may be relative (base dir: [civicrm.files]).
14 * - URLs may be relative (base dir: [cms.root]).
18 const DEFAULT_URL
= 'cms.root';
19 const DEFAULT_PATH
= 'civicrm.files';
23 * Array(string $name => array(url => $, path => $)).
25 private $variables = array();
27 private $variableFactory = array();
29 public function __construct() {
31 ->register('civicrm.root', function () {
32 return \CRM_Core_Config
::singleton()->userSystem
->getCiviSourceStorage();
34 ->register('civicrm.files', function () {
35 return \CRM_Core_Config
::singleton()->userSystem
->getDefaultFileStorage();
37 ->register('cms', function () {
39 'path' => \CRM_Core_Config
::singleton()->userSystem
->cmsRootPath(),
40 'url' => \CRM_Utils_System
::baseCMSURL(),
43 ->register('cms.root', function () {
45 'path' => \CRM_Core_Config
::singleton()->userSystem
->cmsRootPath(),
46 // Misleading: this *removes* the language part of the URL, producing a pristine base URL.
47 'url' => \CRM_Utils_System
::languageNegotiationURL(\CRM_Utils_System
::baseCMSURL(), FALSE, TRUE),
53 * Register a new URL/file path mapping.
56 * The name of the variable.
57 * @param callable $factory
58 * Function which returns an array with keys:
63 public function register($name, $factory) {
64 $this->variableFactory
[$name] = $factory;
75 public function getVariable($name, $attr) {
76 if (!isset($this->variables
[$name])) {
77 $this->variables
[$name] = call_user_func($this->variableFactory
[$name]);
79 if (!isset($this->variables
[$name][$attr])) {
80 throw new \
RuntimeException("Cannot resolve path using \"$name.$attr\"");
82 return $this->variables
[$name][$attr];
85 public function hasVariable($name) {
86 return isset($this->variableFactory
[$name]);
90 * Determine the absolute path to a file, given that the file is most likely
91 * in a given particular variable.
93 * @param string $value
95 * Use "." to reference to default file root.
96 * Values may begin with a variable, e.g. "[civicrm.files]/upload".
97 * @return mixed|string
99 public function getPath($value) {
100 $defaultContainer = self
::DEFAULT_PATH
;
101 if ($value && $value{0} == '[' && preg_match(';^\[([a-zA-Z0-9\._]+)\]/(.*);', $value, $matches)) {
102 $defaultContainer = $matches[1];
103 $value = $matches[2];
108 if ($value === '.') {
111 return \CRM_Utils_File
::absoluteDirectory($value, $this->getVariable($defaultContainer, 'path'));
115 * Determine the URL to a file.
117 * @param string $value
118 * The file path. The path may begin with a variable, e.g. "[civicrm.files]/upload".
119 * @param string $preferFormat
120 * The preferred format ('absolute', 'relative').
121 * The result data may not meet the preference -- if the setting
122 * refers to an external domain, then the result will be
123 * absolute (regardless of preference).
124 * @param bool|NULL $ssl
125 * NULL to autodetect. TRUE to force to SSL.
126 * @return mixed|string
128 public function getUrl($value, $preferFormat = 'relative', $ssl = NULL) {
129 $defaultContainer = self
::DEFAULT_URL
;
130 if ($value && $value{0} == '[' && preg_match(';^\[([a-zA-Z0-9\._]+)\](/(.*))$;', $value, $matches)) {
131 $defaultContainer = $matches[1];
132 $value = empty($matches[3]) ?
'.' : $matches[3];
138 if ($value === '.') {
141 if (substr($value, 0, 4) == 'http') {
145 $value = $this->getVariable($defaultContainer, 'url') . $value;
147 if ($preferFormat === 'relative') {
148 $parsed = parse_url($value);
149 if (isset($_SERVER['HTTP_HOST']) && isset($parsed['host']) && $_SERVER['HTTP_HOST'] == $parsed['host']) {
150 $value = $parsed['path'];
154 if ($ssl ||
($ssl === NULL && \CRM_Utils_System
::isSSL())) {
155 $value = str_replace('http://', 'https://', $value);