Merge pull request #16414 from eileenmcnaughton/acl
[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 = [];
26
27 private $variableFactory = [];
28
29 /**
30 * Class constructor.
31 */
32 public function __construct() {
33 $paths = $this;
34 $this
35 ->register('civicrm.root', function () {
36 return \CRM_Core_Config::singleton()->userSystem->getCiviSourceStorage();
37 })
38 ->register('civicrm.packages', function () {
39 return [
40 'path' => \Civi::paths()->getPath('[civicrm.root]/packages/'),
41 'url' => \Civi::paths()->getUrl('[civicrm.root]/packages/'),
42 ];
43 })
44 ->register('civicrm.vendor', function () {
45 return [
46 'path' => \Civi::paths()->getPath('[civicrm.root]/vendor/'),
47 'url' => \Civi::paths()->getUrl('[civicrm.root]/vendor/'),
48 ];
49 })
50 ->register('civicrm.bower', function () {
51 return [
52 'path' => \Civi::paths()->getPath('[civicrm.root]/bower_components/'),
53 'url' => \Civi::paths()->getUrl('[civicrm.root]/bower_components/'),
54 ];
55 })
56 ->register('civicrm.files', function () {
57 return \CRM_Core_Config::singleton()->userSystem->getDefaultFileStorage();
58 })
59 ->register('civicrm.private', function () {
60 return [
61 // For backward compatibility with existing deployments, this
62 // effectively returns `dirname(CIVICRM_TEMPLATE_COMPILEDIR)`.
63 // That's confusing. Future installers should probably set `civicrm.private`
64 // explicitly instead of setting `CIVICRM_TEMPLATE_COMPILEDIR`.
65 'path' => \CRM_Utils_File::baseFilePath(),
66 ];
67 })
68 ->register('civicrm.log', function () {
69 return [
70 'path' => \Civi::paths()->getPath('[civicrm.private]/ConfigAndLog'),
71 ];
72 })
73 ->register('civicrm.compile', function () {
74 return [
75 // These two formulations are equivalent in typical deployments; however,
76 // for existing systems which previously customized CIVICRM_TEMPLATE_COMPILEDIR,
77 // using the constant should be more backward-compatibility.
78 'path' => defined('CIVICRM_TEMPLATE_COMPILEDIR') ? CIVICRM_TEMPLATE_COMPILEDIR : \Civi::paths()->getPath('[civicrm.private]/templates_c'),
79 ];
80 })
81 ->register('civicrm.l10n', function () {
82 $dir = defined('CIVICRM_L10N_BASEDIR') ? CIVICRM_L10N_BASEDIR : \Civi::paths()->getPath('[civicrm.private]/l10n');
83 return [
84 'path' => is_dir($dir) ? $dir : \Civi::paths()->getPath('[civicrm.root]/l10n'),
85 ];
86 })
87 ->register('wp.frontend.base', function () {
88 return ['url' => rtrim(CIVICRM_UF_BASEURL, '/') . '/'];
89 })
90 ->register('wp.frontend', function () use ($paths) {
91 $config = \CRM_Core_Config::singleton();
92 $suffix = defined('CIVICRM_UF_WP_BASEPAGE') ? CIVICRM_UF_WP_BASEPAGE : $config->wpBasePage;
93 return [
94 'url' => $paths->getVariable('wp.frontend.base', 'url') . $suffix,
95 ];
96 })
97 ->register('wp.backend.base', function () {
98 return ['url' => rtrim(CIVICRM_UF_BASEURL, '/') . '/wp-admin/'];
99 })
100 ->register('wp.backend', function () use ($paths) {
101 return [
102 'url' => $paths->getVariable('wp.backend.base', 'url') . 'admin.php',
103 ];
104 })
105 ->register('cms', function () {
106 return [
107 'path' => \CRM_Core_Config::singleton()->userSystem->cmsRootPath(),
108 'url' => \CRM_Utils_System::baseCMSURL(),
109 ];
110 })
111 ->register('cms.root', function () {
112 return [
113 'path' => \CRM_Core_Config::singleton()->userSystem->cmsRootPath(),
114 // Misleading: this *removes* the language part of the URL, producing a pristine base URL.
115 'url' => \CRM_Utils_System::languageNegotiationURL(\CRM_Utils_System::baseCMSURL(), FALSE, TRUE),
116 ];
117 });
118 }
119
120 /**
121 * Register a new URL/file path mapping.
122 *
123 * @param string $name
124 * The name of the variable.
125 * @param callable $factory
126 * Function which returns an array with keys:
127 * - path: string.
128 * - url: string.
129 * @return Paths
130 */
131 public function register($name, $factory) {
132 $this->variableFactory[$name] = $factory;
133 return $this;
134 }
135
136 /**
137 * @param string $name
138 * Ex: 'civicrm.root'.
139 * @param string $attr
140 * Ex: 'url', 'path'.
141 * @return mixed
142 */
143 public function getVariable($name, $attr) {
144 if (!isset($this->variables[$name])) {
145 $this->variables[$name] = call_user_func($this->variableFactory[$name]);
146 if (isset($GLOBALS['civicrm_paths'][$name])) {
147 $this->variables[$name] = array_merge($this->variables[$name], $GLOBALS['civicrm_paths'][$name]);
148 }
149 }
150 if (!isset($this->variables[$name][$attr])) {
151 throw new \RuntimeException("Cannot resolve path using \"$name.$attr\"");
152 }
153 return $this->variables[$name][$attr];
154 }
155
156 /**
157 * Does the variable exist.
158 *
159 * @param string $name
160 *
161 * @return bool
162 */
163 public function hasVariable($name) {
164 return isset($this->variableFactory[$name]);
165 }
166
167 /**
168 * Determine the absolute path to a file, given that the file is most likely
169 * in a given particular variable.
170 *
171 * @param string $value
172 * The file path.
173 * Use "." to reference to default file root.
174 * Values may begin with a variable, e.g. "[civicrm.files]/upload".
175 * @return mixed|string
176 */
177 public function getPath($value) {
178 $defaultContainer = self::DEFAULT_PATH;
179 if ($value && $value{0} == '[' && preg_match(';^\[([a-zA-Z0-9\._]+)\]/(.*);', $value, $matches)) {
180 $defaultContainer = $matches[1];
181 $value = $matches[2];
182 }
183 if (empty($value)) {
184 return FALSE;
185 }
186 if ($value === '.') {
187 $value = '';
188 }
189 return \CRM_Utils_File::absoluteDirectory($value, $this->getVariable($defaultContainer, 'path'));
190 }
191
192 /**
193 * Determine the URL to a file.
194 *
195 * @param string $value
196 * The file path. The path may begin with a variable, e.g. "[civicrm.files]/upload".
197 * @param string $preferFormat
198 * The preferred format ('absolute', 'relative').
199 * The result data may not meet the preference -- if the setting
200 * refers to an external domain, then the result will be
201 * absolute (regardless of preference).
202 * @param bool|NULL $ssl
203 * NULL to autodetect. TRUE to force to SSL.
204 * @return mixed|string
205 */
206 public function getUrl($value, $preferFormat = 'relative', $ssl = NULL) {
207 $defaultContainer = self::DEFAULT_URL;
208 if ($value && $value{0} == '[' && preg_match(';^\[([a-zA-Z0-9\._]+)\](/(.*))$;', $value, $matches)) {
209 $defaultContainer = $matches[1];
210 $value = empty($matches[3]) ? '.' : $matches[3];
211 }
212
213 if (empty($value)) {
214 return FALSE;
215 }
216 if ($value === '.') {
217 $value = '';
218 }
219 if (substr($value, 0, 4) == 'http') {
220 return $value;
221 }
222
223 $value = rtrim($this->getVariable($defaultContainer, 'url'), '/') . '/' . $value;
224
225 if ($preferFormat === 'relative') {
226 $parsed = parse_url($value);
227 if (isset($_SERVER['HTTP_HOST']) && isset($parsed['host']) && $_SERVER['HTTP_HOST'] == $parsed['host']) {
228 $value = $parsed['path'];
229 }
230 }
231
232 if ($ssl || ($ssl === NULL && \CRM_Utils_System::isSSL())) {
233 $value = str_replace('http://', 'https://', $value);
234 }
235
236 return $value;
237 }
238
239 }