Merge pull request #10569 from eileenmcnaughton/perm
[civicrm-core.git] / Civi / Core / Paths.php
1 <?php
2 namespace Civi\Core;
3
4 /**
5 * Class Paths
6 * @package Civi\Core
7 *
8 * This paths class translates path-expressions into local file paths and
9 * URLs. Path-expressions may take a few forms:
10 *
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]).
15 */
16 class Paths {
17
18 const DEFAULT_URL = 'cms.root';
19 const DEFAULT_PATH = 'civicrm.files';
20
21 /**
22 * @var array
23 * Array(string $name => array(url => $, path => $)).
24 */
25 private $variables = array();
26
27 private $variableFactory = array();
28
29 /**
30 * Class constructor.
31 */
32 public function __construct() {
33 $this
34 ->register('civicrm.root', function () {
35 return \CRM_Core_Config::singleton()->userSystem->getCiviSourceStorage();
36 })
37 ->register('civicrm.packages', function () {
38 return array(
39 'path' => \Civi::paths()->getPath('[civicrm.root]/packages/'),
40 'url' => \Civi::paths()->getUrl('[civicrm.root]/packages/'),
41 );
42 })
43 ->register('civicrm.vendor', function () {
44 return array(
45 'path' => \Civi::paths()->getPath('[civicrm.root]/vendor/'),
46 'url' => \Civi::paths()->getUrl('[civicrm.root]/vendor/'),
47 );
48 })
49 ->register('civicrm.bower', function () {
50 return array(
51 'path' => \Civi::paths()->getPath('[civicrm.root]/bower_components/'),
52 'url' => \Civi::paths()->getUrl('[civicrm.root]/bower_components/'),
53 );
54 })
55 ->register('civicrm.files', function () {
56 return \CRM_Core_Config::singleton()->userSystem->getDefaultFileStorage();
57 })
58 ->register('cms', function () {
59 return array(
60 'path' => \CRM_Core_Config::singleton()->userSystem->cmsRootPath(),
61 'url' => \CRM_Utils_System::baseCMSURL(),
62 );
63 })
64 ->register('cms.root', function () {
65 return array(
66 'path' => \CRM_Core_Config::singleton()->userSystem->cmsRootPath(),
67 // Misleading: this *removes* the language part of the URL, producing a pristine base URL.
68 'url' => \CRM_Utils_System::languageNegotiationURL(\CRM_Utils_System::baseCMSURL(), FALSE, TRUE),
69 );
70 });
71 }
72
73 /**
74 * Register a new URL/file path mapping.
75 *
76 * @param string $name
77 * The name of the variable.
78 * @param callable $factory
79 * Function which returns an array with keys:
80 * - path: string.
81 * - url: string.
82 * @return Paths
83 */
84 public function register($name, $factory) {
85 $this->variableFactory[$name] = $factory;
86 return $this;
87 }
88
89 /**
90 * @param string $name
91 * Ex: 'civicrm.root'.
92 * @param string $attr
93 * Ex: 'url', 'path'.
94 * @return mixed
95 */
96 public function getVariable($name, $attr) {
97 if (!isset($this->variables[$name])) {
98 $this->variables[$name] = call_user_func($this->variableFactory[$name]);
99 if (isset($GLOBALS['civicrm_paths'][$name])) {
100 $this->variables[$name] = array_merge($this->variables[$name], $GLOBALS['civicrm_paths'][$name]);
101 }
102 }
103 if (!isset($this->variables[$name][$attr])) {
104 throw new \RuntimeException("Cannot resolve path using \"$name.$attr\"");
105 }
106 return $this->variables[$name][$attr];
107 }
108
109 /**
110 * Does the variable exist.
111 *
112 * @param string $name
113 *
114 * @return bool
115 */
116 public function hasVariable($name) {
117 return isset($this->variableFactory[$name]);
118 }
119
120 /**
121 * Determine the absolute path to a file, given that the file is most likely
122 * in a given particular variable.
123 *
124 * @param string $value
125 * The file path.
126 * Use "." to reference to default file root.
127 * Values may begin with a variable, e.g. "[civicrm.files]/upload".
128 * @return mixed|string
129 */
130 public function getPath($value) {
131 $defaultContainer = self::DEFAULT_PATH;
132 if ($value && $value{0} == '[' && preg_match(';^\[([a-zA-Z0-9\._]+)\]/(.*);', $value, $matches)) {
133 $defaultContainer = $matches[1];
134 $value = $matches[2];
135 }
136 if (empty($value)) {
137 return FALSE;
138 }
139 if ($value === '.') {
140 $value = '';
141 }
142 return \CRM_Utils_File::absoluteDirectory($value, $this->getVariable($defaultContainer, 'path'));
143 }
144
145 /**
146 * Determine the URL to a file.
147 *
148 * @param string $value
149 * The file path. The path may begin with a variable, e.g. "[civicrm.files]/upload".
150 * @param string $preferFormat
151 * The preferred format ('absolute', 'relative').
152 * The result data may not meet the preference -- if the setting
153 * refers to an external domain, then the result will be
154 * absolute (regardless of preference).
155 * @param bool|NULL $ssl
156 * NULL to autodetect. TRUE to force to SSL.
157 * @return mixed|string
158 */
159 public function getUrl($value, $preferFormat = 'relative', $ssl = NULL) {
160 $defaultContainer = self::DEFAULT_URL;
161 if ($value && $value{0} == '[' && preg_match(';^\[([a-zA-Z0-9\._]+)\](/(.*))$;', $value, $matches)) {
162 $defaultContainer = $matches[1];
163 $value = empty($matches[3]) ? '.' : $matches[3];
164 }
165
166 if (empty($value)) {
167 return FALSE;
168 }
169 if ($value === '.') {
170 $value = '';
171 }
172 if (substr($value, 0, 4) == 'http') {
173 return $value;
174 }
175
176 $value = $this->getVariable($defaultContainer, 'url') . $value;
177
178 if ($preferFormat === 'relative') {
179 $parsed = parse_url($value);
180 if (isset($_SERVER['HTTP_HOST']) && isset($parsed['host']) && $_SERVER['HTTP_HOST'] == $parsed['host']) {
181 $value = $parsed['path'];
182 }
183 }
184
185 if ($ssl || ($ssl === NULL && \CRM_Utils_System::isSSL())) {
186 $value = str_replace('http://', 'https://', $value);
187 }
188
189 return $value;
190 }
191
192 }